| | |
| | | import { |
| | | NButton, |
| | | NCard, |
| | | NDataTable, |
| | | NForm, |
| | | NFormItem, |
| | | NGrid, |
| | |
| | | NTabPane, |
| | | NTabs, |
| | | NTree, |
| | | NDataTable, |
| | | } from 'naive-ui' |
| | | import { |
| | | createPermissionGroup, |
| | | fetchGroupUserPageList, |
| | | getPermissionGroupDetail as fetchPermissionGroupDetail, |
| | | getMenuList, |
| | | updatePermissionGroup, |
| | | getMenuList |
| | | } from '@/service' |
| | | |
| | | // 用于生成唯一的key |
| | |
| | | |
| | | // 详情页主 Tab |
| | | const mainTab = ref<'permission' | 'accounts'>('permission') |
| | | |
| | | // 当前选中的端 1=管理端 2=运维app |
| | | const activeTab = ref<'management' | 'operation'>('management') |
| | | const groupType = computed(() => (activeTab.value === 'management' ? 1 : 2)) |
| | | |
| | | function handleTabChange(value: 'management' | 'operation') { |
| | | activeTab.value = value |
| | | ensureMenuTreeLoaded(value) |
| | | } |
| | | |
| | | // 关联账户表格数据 |
| | | const userList = ref([]) |
| | |
| | | } |
| | | }) |
| | | |
| | | // 当前选中的端 1=管理端 2=运维app |
| | | const activeTab = ref<'management' | 'operation'>('management') |
| | | const groupType = computed(() => (activeTab.value === 'management' ? 1 : 2)) |
| | | |
| | | // 处理标签页切换 |
| | | function handleTabChange(value: 'management' | 'operation') { |
| | | activeTab.value = value |
| | | ensureMenuTreeLoaded(value) |
| | | } |
| | | |
| | | // 表单数据 |
| | | const formModel = ref({ |
| | | groupName: '', |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | // 转换树数据,为每个节点生成唯一 key |
| | | function transformTreeData(nodes: any[]): any[] { |
| | | return nodes.map(node => { |
| | | return nodes.map((node) => { |
| | | const key = generateUniqueKey() |
| | | const newNode = { |
| | | ...node, |
| | |
| | | |
| | | for (const child of node.children) { |
| | | const status = processNode(child) |
| | | if (status === 1) hasChecked = true |
| | | else if (status === 0) hasUnchecked = true |
| | | else hasIndeterminate = true |
| | | if (status === 1) |
| | | hasChecked = true |
| | | else if (status === 0) |
| | | hasUnchecked = true |
| | | else |
| | | hasIndeterminate = true |
| | | } |
| | | |
| | | if (hasIndeterminate || (hasChecked && hasUnchecked)) { |
| | |
| | | |
| | | // 从 keys 中还原出原始 ID |
| | | const allKeys = new Set([...checkedKeys, ...indeterminateKeys]) |
| | | const menuIds = Array.from(allKeys).map(key => { |
| | | const menuIds = Array.from(allKeys).map((key) => { |
| | | const node = nodeMap.get(key as string) |
| | | return node ? node.id : null |
| | | }).filter(id => id !== null) |
| | |
| | | </div> |
| | | |
| | | <NCard> |
| | | <NForm ref="formRef" :model="formModel" :rules="rules" label-placement="top" label-width="120" require-mark-placement="left" |
| | | :disabled="mode === 'view'"> |
| | | <NForm |
| | | ref="formRef" |
| | | :model="formModel" |
| | | :rules="rules" |
| | | label-placement="top" |
| | | label-width="120" |
| | | require-mark-placement="left" |
| | | :disabled="mode === 'view'" |
| | | > |
| | | <!-- 基本信息 --> |
| | | <NGrid :cols="24" :x-gap="24"> |
| | | <NGridItem :span="8"> |
| | |
| | | <NTabPane name="accounts" tab="关联用户"> |
| | | <div class="user-search-bar" style="margin-bottom: 16px;"> |
| | | <NSpace align="center"> |
| | | <NInput v-model:value="userSearchModel.nickName" placeholder="用户名称" clearable style="width: 200px" |
| | | :disabled="false" |
| | | @keyup.enter="handleUserSearch" /> |
| | | <NInput v-model:value="userSearchModel.phoneNumber" placeholder="用户电话" clearable |
| | | <NInput |
| | | v-model:value="userSearchModel.nickName" |
| | | placeholder="用户名称" |
| | | clearable |
| | | style="width: 200px" |
| | | :disabled="false" |
| | | @keyup.enter="handleUserSearch" /> |
| | | @keyup.enter="handleUserSearch" |
| | | /> |
| | | <NInput |
| | | v-model:value="userSearchModel.phoneNumber" |
| | | placeholder="用户电话" |
| | | clearable |
| | | style="width: 200px" |
| | | :disabled="false" |
| | | @keyup.enter="handleUserSearch" |
| | | /> |
| | | <NButton type="primary" :disabled="false" @click="handleUserSearch"> |
| | | 查询 |
| | | </NButton> |
| | |
| | | </NButton> |
| | | </NSpace> |
| | | </div> |
| | | <NDataTable :columns="userColumns" :data="userList" :border="false" :loading="userLoading" :pagination="userPagination" |
| | | remote /> |
| | | <NDataTable |
| | | :columns="userColumns" |
| | | :data="userList" |
| | | :border="false" |
| | | :loading="userLoading" |
| | | :pagination="userPagination" |
| | | remote |
| | | /> |
| | | </NTabPane> |
| | | </NTabs> |
| | | |
| | |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | | |
| | | <style lang="scss"> |
| | | .tree-container { |
| | | .n-tree { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | </style> |
| | |
| | | <script setup lang="tsx"> |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { computed, onMounted, ref, watch } from 'vue' |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { useMessage } from 'naive-ui' |
| | | import type { FormInst, FormRules } from 'naive-ui' |
| | | import { useDialog, useMessage } from 'naive-ui' |
| | | import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue' |
| | | import { addCoupon, fetchCouponDetail } from '@/service/api/coupon' |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const message = useMessage() |
| | | const dialog = useDialog() |
| | | const loading = ref(false) |
| | | const initialSnapshot = ref('') |
| | | const formRef = ref<FormInst | null>(null) |
| | | |
| | | const isReadonly = computed(() => route.query.mode === 'detail') |
| | | const couponId = computed(() => { |
| | |
| | | { label: '满减券', value: 2 }, |
| | | ] |
| | | |
| | | const rules: FormRules = { |
| | | name: [ |
| | | { required: true, message: '请输入优惠券名称', trigger: ['blur', 'input'] }, |
| | | { |
| | | validator: (_rule, value) => { |
| | | const raw = (value ?? '').toString().trim() |
| | | if (!raw) |
| | | return true |
| | | if (raw.length > 20) |
| | | return new Error('最多20个字') |
| | | return true |
| | | }, |
| | | trigger: ['blur', 'input'], |
| | | }, |
| | | ], |
| | | type: [ |
| | | { required: true, type: 'number', message: '请选择优惠券类型', trigger: 'change' }, |
| | | ], |
| | | conditionAmount: [ |
| | | { |
| | | validator: (_rule, value) => { |
| | | if (formModel.value.type !== 2) |
| | | return true |
| | | if (value === null || value === undefined) |
| | | return new Error('请输入满减条件金额') |
| | | const num = Number(value) |
| | | if (!Number.isFinite(num)) |
| | | return new Error('请输入有效的条件金额') |
| | | if (num <= 0) |
| | | return new Error('条件金额必须大于0') |
| | | return true |
| | | }, |
| | | trigger: ['blur', 'change'], |
| | | }, |
| | | ], |
| | | amount: [ |
| | | { |
| | | required: true, |
| | | validator: (_rule, value) => { |
| | | if (value === null || value === undefined) |
| | | return new Error('请输入优惠金额') |
| | | const amount = Number(value) |
| | | if (!Number.isFinite(amount)) |
| | | return new Error('请输入有效的优惠金额') |
| | | if (amount <= 0) |
| | | return new Error('优惠金额必须大于0') |
| | | |
| | | if (!formModel.value.type) |
| | | return new Error('请先选择优惠券类型') |
| | | |
| | | if (formModel.value.type === 2) { |
| | | const condition = formModel.value.conditionAmount |
| | | if (condition === null || condition === undefined) |
| | | return true |
| | | const conditionNum = Number(condition) |
| | | if (Number.isFinite(conditionNum) && amount >= conditionNum) |
| | | return new Error('满减金额必须小于条件金额') |
| | | } |
| | | |
| | | return true |
| | | }, |
| | | trigger: ['blur', 'change'], |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | function safeDecode(value: string) { |
| | | try { |
| | | return decodeURIComponent(value) |
| | |
| | | } |
| | | } |
| | | |
| | | function getSnapshot() { |
| | | return JSON.stringify({ |
| | | name: formModel.value.name ?? '', |
| | | type: formModel.value.type ?? null, |
| | | conditionAmount: formModel.value.conditionAmount ?? null, |
| | | amount: formModel.value.amount ?? null, |
| | | description: formModel.value.description ?? '', |
| | | }) |
| | | } |
| | | |
| | | function hasUnsavedInput() { |
| | | if (isReadonly.value) |
| | | return false |
| | | if (!initialSnapshot.value) |
| | | return false |
| | | return getSnapshot() !== initialSnapshot.value |
| | | } |
| | | |
| | | async function handleSave() { |
| | | if (isReadonly.value) |
| | | return |
| | | |
| | | if (!formModel.value.name) { |
| | | message.warning('请输入优惠券名称') |
| | | return |
| | | try { |
| | | await formRef.value?.validate() |
| | | } |
| | | if (!formModel.value.type) { |
| | | message.warning('请选择优惠券类型') |
| | | return |
| | | } |
| | | if (formModel.value.type === 2 && (formModel.value.conditionAmount === null || formModel.value.conditionAmount === undefined)) { |
| | | message.warning('请输入满减条件金额') |
| | | return |
| | | } |
| | | if (formModel.value.amount === null || formModel.value.amount === undefined) { |
| | | message.warning('请输入优惠金额') |
| | | catch { |
| | | return |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function handleBack() { |
| | | router.back() |
| | | if (!hasUnsavedInput()) { |
| | | router.back() |
| | | return |
| | | } |
| | | |
| | | dialog.warning({ |
| | | title: '确认取消创建吗?', |
| | | content: '当前优惠券未保存,确认取消创建吗?', |
| | | positiveText: '确定取消', |
| | | negativeText: '继续编辑', |
| | | onPositiveClick: () => { |
| | | router.back() |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | async function getDetail() { |
| | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDetail() |
| | | getDetail().finally(() => { |
| | | initialSnapshot.value = getSnapshot() |
| | | }) |
| | | }) |
| | | |
| | | watch( |
| | | () => formModel.value.type, |
| | | () => { |
| | | formRef.value?.validate(undefined, (rule) => { |
| | | return rule?.key === 'conditionAmount' || rule?.key === 'amount' |
| | | }) |
| | | }, |
| | | ) |
| | | |
| | | watch( |
| | | () => formModel.value.conditionAmount, |
| | | () => { |
| | | formRef.value?.validate(undefined, (rule) => { |
| | | return rule?.key === 'amount' |
| | | }) |
| | | }, |
| | | ) |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | <n-form |
| | | ref="formRef" |
| | | :model="formModel" |
| | | :rules="rules" |
| | | label-placement="left" |
| | | label-width="auto" |
| | | label-align="left" |
| | |
| | | v-model:value="formModel.amount" |
| | | :disabled="isReadonly" |
| | | placeholder="最多4位数字" |
| | | :min="0" |
| | | :min="0.01" |
| | | :max="9999" |
| | | :precision="2" |
| | | clearable |
| | |
| | | <script setup lang="ts"> |
| | | import { ref, watch } from 'vue' |
| | | import type { FormInst, FormRules, UploadFileInfo, UploadCustomRequestOptions } from 'naive-ui' |
| | | import { useMessage } from 'naive-ui' |
| | | import { addBanner, updateBanner, type Banner } from '@/service/api/banner' |
| | | import type { FormInst, FormRules, UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui' |
| | | import { useDialog, useMessage } from 'naive-ui' |
| | | import { addBanner, updateBanner } from '@/service/api/banner' |
| | | import type { Banner } from '@/service/api/banner' |
| | | import { customUploadRequest } from '@/utils/upload' |
| | | import dayjs from 'dayjs' |
| | | |
| | |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | mode: 'add', |
| | | rowData: null |
| | | rowData: null, |
| | | }) |
| | | |
| | | const emit = defineEmits<{ |
| | | 'update:show': [value: boolean] |
| | | 'confirm': [] |
| | | 'switch-edit': [] |
| | | 'switchEdit': [] |
| | | }>() |
| | | |
| | | const message = useMessage() |
| | | const dialog = useDialog() |
| | | const formRef = ref<FormInst | null>(null) |
| | | const fileList = ref<UploadFileInfo[]>([]) |
| | | const initialSnapshot = ref('') |
| | | const initialStartTime = ref<number | null>(null) |
| | | |
| | | const bannerImageWidth = 750 |
| | | const bannerImageHeight = 200 |
| | | |
| | | function getImageDimensions(file: File) { |
| | | return new Promise<{ width: number, height: number }>((resolve, reject) => { |
| | | const url = URL.createObjectURL(file) |
| | | const img = new Image() |
| | | img.onload = () => { |
| | | resolve({ width: img.naturalWidth, height: img.naturalHeight }) |
| | | URL.revokeObjectURL(url) |
| | | } |
| | | img.onerror = () => { |
| | | reject(new Error('load image failed')) |
| | | URL.revokeObjectURL(url) |
| | | } |
| | | img.src = url |
| | | }) |
| | | } |
| | | |
| | | interface FormModel { |
| | | bannerTitle: string |
| | |
| | | appletName: '', |
| | | appletId: '', |
| | | jumpUrl: '', |
| | | bannerStatus: true |
| | | bannerStatus: true, |
| | | }) |
| | | |
| | | const jumpTypeOptions = [ |
| | | { label: '小程序内页面', value: 1 }, |
| | | { label: '小程序地址', value: 2 }, |
| | | { label: '网页地址', value: 3 } |
| | | { label: '网页地址', value: 3 }, |
| | | ] |
| | | |
| | | const rules: FormRules = { |
| | | bannerPictures: [ |
| | | { required: true, message: '请上传广告图', trigger: 'change' } |
| | | { required: true, message: '请上传广告图', trigger: 'change' }, |
| | | ], |
| | | bannerTitle: [ |
| | | { required: true, message: '请输入广告标题', trigger: 'blur' } |
| | | { required: true, message: '请输入广告标题', trigger: ['blur', 'input'] }, |
| | | { |
| | | validator(_rule, value) { |
| | | const raw = (value ?? '').toString() |
| | | if (!raw) |
| | | return true |
| | | if (raw.length > 30) { |
| | | return new Error('广告标题长度不超过30个字符') |
| | | } |
| | | return true |
| | | }, |
| | | trigger: ['blur', 'input'], |
| | | }, |
| | | ], |
| | | sort: [ |
| | | { |
| | |
| | | } |
| | | return true |
| | | }, |
| | | trigger: 'change' |
| | | } |
| | | trigger: 'change', |
| | | }, |
| | | ], |
| | | startTime: [ |
| | | { required: true, type: 'number', message: '请选择广告开始时间', trigger: 'change' } |
| | | { |
| | | required: true, |
| | | validator(_rule, value) { |
| | | if (value === null || value === undefined || value === '') { |
| | | return new Error('请选择广告开始时间') |
| | | } |
| | | if (typeof value !== 'number') { |
| | | return new Error('请选择广告开始时间') |
| | | } |
| | | const now = Date.now() |
| | | if (value < now) { |
| | | if (props.mode !== 'add' && value === initialStartTime.value) { |
| | | return true |
| | | } |
| | | return new Error('开始时间不能早于当前时间') |
| | | } |
| | | return true |
| | | }, |
| | | trigger: ['blur', 'change'], |
| | | }, |
| | | ], |
| | | endTime: [ |
| | | { required: true, type: 'number', message: '请选择广告结束时间', trigger: 'change' } |
| | | { |
| | | required: true, |
| | | validator(_rule, value) { |
| | | if (value === null || value === undefined || value === '') { |
| | | return new Error('请选择广告结束时间') |
| | | } |
| | | if (typeof value !== 'number') { |
| | | return new Error('请选择广告结束时间') |
| | | } |
| | | const start = formModel.value.startTime |
| | | if (typeof start === 'number' && value < start) { |
| | | return new Error('结束时间不能小于开始时间') |
| | | } |
| | | return true |
| | | }, |
| | | trigger: ['blur', 'change'], |
| | | }, |
| | | ], |
| | | jumpType: [ |
| | | { required: true, type: 'number', message: '请选择跳转类型', trigger: 'change' } |
| | | { required: true, type: 'number', message: '请选择跳转类型', trigger: 'change' }, |
| | | ], |
| | | appletName: [ |
| | | { |
| | |
| | | } |
| | | return true |
| | | }, |
| | | trigger: 'blur' |
| | | } |
| | | trigger: 'blur', |
| | | }, |
| | | ], |
| | | appletId: [ |
| | | { |
| | |
| | | } |
| | | return true |
| | | }, |
| | | trigger: 'blur' |
| | | } |
| | | trigger: 'blur', |
| | | }, |
| | | ], |
| | | jumpUrl: [ |
| | | { |
| | |
| | | } |
| | | return true |
| | | }, |
| | | trigger: 'blur' |
| | | } |
| | | ] |
| | | trigger: 'blur', |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | const handleCustomUpload = async (options: UploadCustomRequestOptions) => { |
| | | async function handleCustomUpload(options: UploadCustomRequestOptions) { |
| | | const rawFile = (options.file as any)?.file as File | undefined |
| | | if (rawFile) { |
| | | const allowedTypes = ['image/jpeg', 'image/png'] |
| | | if (!allowedTypes.includes(rawFile.type)) { |
| | | message.error('仅支持 jpg、png 格式') |
| | | formModel.value.bannerPictures = '' |
| | | fileList.value = [] |
| | | options.onError?.() |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const { width, height } = await getImageDimensions(rawFile) |
| | | if (width !== bannerImageWidth || height !== bannerImageHeight) { |
| | | message.error(`请上传尺寸为 ${bannerImageWidth} x ${bannerImageHeight} 的图片`) |
| | | formModel.value.bannerPictures = '' |
| | | fileList.value = [] |
| | | options.onError?.() |
| | | return |
| | | } |
| | | } |
| | | catch { |
| | | message.error('图片读取失败,请重试') |
| | | formModel.value.bannerPictures = '' |
| | | fileList.value = [] |
| | | options.onError?.() |
| | | return |
| | | } |
| | | } |
| | | |
| | | const result = await customUploadRequest(options as any) |
| | | console.log('上传结果', result) |
| | | if (result) { |
| | | // 构建 JSON 数组字符串 |
| | | const pictureData = [{ |
| | | id: result.id, |
| | | name: result.name, |
| | | url: result.url |
| | | url: result.url, |
| | | }] |
| | | formModel.value.bannerPictures = JSON.stringify(pictureData) |
| | | fileList.value = [{ |
| | | id: result.id, |
| | | name: result.name, |
| | | status: 'finished', |
| | | url: result.url |
| | | url: result.url, |
| | | }] |
| | | // 触发校验 |
| | | formRef.value?.validate(errors => { |
| | | if (!errors) return |
| | | }, (rule) => rule?.key === 'bannerPictures') |
| | | formRef.value?.validate(undefined, (rule) => { |
| | | return rule?.key === 'bannerPictures' |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const handleRemove = () => { |
| | | function handleRemove() { |
| | | formModel.value.bannerPictures = '' |
| | | fileList.value = [] |
| | | } |
| | | |
| | | const handleClose = () => { |
| | | function closeModal() { |
| | | emit('update:show', false) |
| | | } |
| | | |
| | | const handleSwitchEdit = () => { |
| | | emit('switch-edit') |
| | | function getSnapshot() { |
| | | const m = formModel.value |
| | | return JSON.stringify({ |
| | | bannerTitle: m.bannerTitle ?? '', |
| | | bannerPictures: m.bannerPictures ?? '', |
| | | sort: m.sort ?? null, |
| | | startTime: m.startTime ?? null, |
| | | endTime: m.endTime ?? null, |
| | | jumpType: m.jumpType ?? 1, |
| | | appletName: m.appletName ?? '', |
| | | appletId: m.appletId ?? '', |
| | | jumpUrl: m.jumpUrl ?? '', |
| | | bannerStatus: m.bannerStatus ?? true, |
| | | }) |
| | | } |
| | | |
| | | const handleConfirm = async () => { |
| | | function handleRequestClose() { |
| | | if (props.mode === 'view') { |
| | | closeModal() |
| | | return |
| | | } |
| | | |
| | | if (initialSnapshot.value && getSnapshot() === initialSnapshot.value) { |
| | | closeModal() |
| | | return |
| | | } |
| | | |
| | | if (!initialSnapshot.value) { |
| | | closeModal() |
| | | return |
| | | } |
| | | |
| | | dialog.warning({ |
| | | title: '确认关闭?', |
| | | content: '当前广告配置未保存,确定取消吗?', |
| | | positiveText: '确定关闭', |
| | | negativeText: '继续编辑', |
| | | onPositiveClick: () => { |
| | | closeModal() |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | function handleSwitchEdit() { |
| | | emit('switchEdit') |
| | | } |
| | | |
| | | async function handleConfirm() { |
| | | try { |
| | | await formRef.value?.validate() |
| | | |
| | |
| | | sort: formModel.value.sort ?? 0, |
| | | bannerStatus: formModel.value.bannerStatus ? 1 : 2, |
| | | startTime: dayjs(formModel.value.startTime).format('YYYY-MM-DD HH:mm:ss'), |
| | | endTime: dayjs(formModel.value.endTime).format('YYYY-MM-DD HH:mm:ss') |
| | | endTime: dayjs(formModel.value.endTime).format('YYYY-MM-DD HH:mm:ss'), |
| | | } |
| | | |
| | | let error = null |
| | |
| | | const res = await addBanner(params) as any |
| | | error = res.error |
| | | code = res.code |
| | | } else { |
| | | } |
| | | else { |
| | | const res = await updateBanner({ |
| | | ...params, |
| | | id: props.rowData?.id |
| | | id: props.rowData?.id, |
| | | }) as any |
| | | error = res.error |
| | | code = res.code |
| | |
| | | if (!error && code === 200) { |
| | | message.success(props.mode === 'add' ? '新增成功' : '编辑成功') |
| | | emit('confirm') |
| | | emit('update:show', false) |
| | | closeModal() |
| | | } |
| | | } catch (error) { |
| | | console.log('验证失败', error) |
| | | } |
| | | catch (error) { |
| | | console.error(error) |
| | | } |
| | | } |
| | | |
| | |
| | | id: pictures[0].id, |
| | | name: pictures[0].name, |
| | | status: 'finished', |
| | | url: pictures[0].url |
| | | url: pictures[0].url, |
| | | }] |
| | | } else { |
| | | } |
| | | else { |
| | | fileList.value = [] |
| | | } |
| | | } catch (e) { |
| | | } |
| | | catch { |
| | | fileList.value = [] |
| | | } |
| | | |
| | |
| | | appletName: row.appletName || '', |
| | | appletId: row.appletId || '', |
| | | jumpUrl: row.jumpUrl || '', |
| | | bannerStatus: row.bannerStatus === 1 |
| | | bannerStatus: row.bannerStatus === 1, |
| | | } |
| | | } else { |
| | | initialStartTime.value = formModel.value.startTime |
| | | } |
| | | else { |
| | | // 新增模式 |
| | | formModel.value = { |
| | | bannerTitle: '', |
| | |
| | | appletName: '', |
| | | appletId: '', |
| | | jumpUrl: '', |
| | | bannerStatus: true |
| | | bannerStatus: true, |
| | | } |
| | | fileList.value = [] |
| | | initialStartTime.value = null |
| | | } |
| | | } else { |
| | | formRef.value?.restoreValidation() |
| | | initialSnapshot.value = getSnapshot() |
| | | } |
| | | else { |
| | | formRef.value?.restoreValidation() |
| | | initialSnapshot.value = '' |
| | | initialStartTime.value = null |
| | | } |
| | | }) |
| | | |
| | | watch(() => formModel.value.startTime, () => { |
| | | formRef.value?.validate(undefined, (rule) => { |
| | | return rule?.key === 'endTime' |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | |
| | | :auto-focus="false" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true |
| | | action: true, |
| | | }" |
| | | :content-style="{ |
| | | // padding: '16px 24px 0px 24px', |
| | | // maxHeight: '60vh', |
| | | // overflow: 'auto' |
| | | }" |
| | | @close="handleClose" |
| | | @close="handleRequestClose" |
| | | > |
| | | <n-form |
| | | ref="formRef" |
| | |
| | | :max="1" |
| | | list-type="image-card" |
| | | :custom-request="handleCustomUpload" |
| | | accept="image/png,image/jpeg" |
| | | :disabled="mode === 'view'" |
| | | @remove="handleRemove" |
| | | > |
| | |
| | | </div> |
| | | </n-upload> |
| | | <div style="margin-top: 8px; color: #999; font-size: 12px;"> |
| | | 支持jpg、png 格式 banner广告建议尺寸:X px Xpx<br /> |
| | | 弹出广告建议尺寸:X px Xpx |
| | | 支持 jpg、png 格式,建议尺寸:750 x 200 |
| | | </div> |
| | | </div> |
| | | </n-form-item> |
| | | |
| | | <n-form-item label="广告标题" path="bannerTitle" required> |
| | | <n-input v-model:value="formModel.bannerTitle" placeholder="请输入" /> |
| | | <n-input v-model:value="formModel.bannerTitle" placeholder="请输入" :maxlength="30" show-count /> |
| | | </n-form-item> |
| | | |
| | | <n-form-item label="广告开始时间" path="startTime" required> |
| | |
| | | > |
| | | 编辑 |
| | | </n-button> |
| | | <n-button @click="handleClose" style="padding: 7px 29px; border-radius: 4px;"> |
| | | <n-button @click="handleRequestClose" style="padding: 7px 29px; border-radius: 4px;"> |
| | | {{ mode === 'view' ? '关闭' : '取消' }} |
| | | </n-button> |
| | | <n-button v-if="mode !== 'view'" type="primary" @click="handleConfirm" style="padding: 7px 29px; border-radius: 4px;"> |