From 4180f73c53b4f2e41b9910149bc441065abbeff0 Mon Sep 17 00:00:00 2001 From: shiran Date: Mon, 15 Jun 2026 18:27:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E8=AE=A2=E5=8D=95=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=87=8D=E6=9E=84=E3=80=81=E8=AE=BE=E7=BD=AE=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=A2=9E=E5=BC=BA=E3=80=81=E7=9F=AD=E4=BF=A1=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E6=A8=A1=E6=9D=BF=E7=AE=A1=E7=90=86=E5=8F=8A=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=B8=A0=E9=81=93=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 订单列表重构为卡片式布局并新增筛选功能 - 设置管理支持struct/struct_list类型配置 - 新增短信签名和模板独立管理页面 - 通知渠道新增短信渠道配置 - 产品参数管理优化 Co-authored-by: Cursor --- src/api/admin/noticeChannel.js | 7 + src/api/admin/smsService.js | 94 + src/config/menus.js | 8 + src/router/index.js | 12 + src/views/marketing/UserVoucher.vue | 2 +- src/views/order/OrderList.vue | 844 ++++++--- src/views/product/ProductList.vue | 12 +- .../components/ProductParameterManager.vue | 14 +- .../product/components/ProductPlanManager.vue | 4 +- src/views/sms/SmsService.vue | 34 +- src/views/sms/SmsSignature.vue | 549 ++++++ src/views/sms/SmsTemplate.vue | 632 +++++++ src/views/system/NoticeChannel.vue | 298 ++- src/views/system/SettingManage.vue | 1664 ++++++++++++++++- 14 files changed, 3811 insertions(+), 363 deletions(-) create mode 100644 src/views/sms/SmsSignature.vue create mode 100644 src/views/sms/SmsTemplate.vue diff --git a/src/api/admin/noticeChannel.js b/src/api/admin/noticeChannel.js index c5b62b5..35a5448 100644 --- a/src/api/admin/noticeChannel.js +++ b/src/api/admin/noticeChannel.js @@ -39,3 +39,10 @@ export const updateNoticeTemplate = (data) => { export const deleteNoticeTemplate = (params) => { return http2.delete('/api/v1/admin/notice_message/template/delete', { params }) } + +/** 使用默认参数预览渲染模板 */ +export const previewNoticeTemplate = (data) => { + return http2.post('/api/v1/admin/notice_message/template/default_msg', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} diff --git a/src/api/admin/smsService.js b/src/api/admin/smsService.js index 7fbebae..2dd5b78 100644 --- a/src/api/admin/smsService.js +++ b/src/api/admin/smsService.js @@ -1,5 +1,7 @@ import { http2 } from '@/utils/request.js' +const formHeaders = { headers: { 'Content-Type': 'multipart/form-data' } } + // ========== 短信主控服务 ========== export const getSmsServiceList = (params) => { @@ -25,6 +27,12 @@ export const deleteSmsService = (data) => { }) } +export const setDefaultSmsService = (data) => { + return http2.post('/api/v1/admin/server/sms_service/set_default', data, { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + }) +} + // ========== 短信额度商品 ========== export const getSmsGoodsList = (params) => { @@ -49,3 +57,89 @@ export const deleteSmsGoods = (data) => { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) } + +// ========== 短信签名管理 ========== + +export const getSmsSignatureList = (params) => { + return http2.get('/api/v1/admin/server/sms_service/signature/list', { params }) +} + +export const getSmsSignatureDetail = (params) => { + return http2.get('/api/v1/admin/server/sms_service/signature/detail', { params }) +} + +export const createSmsSignature = (data) => { + return http2.post('/api/v1/admin/server/sms_service/signature/create', data, formHeaders) +} + +export const updateSmsSignature = (data) => { + return http2.post('/api/v1/admin/server/sms_service/signature/update', data, formHeaders) +} + +export const deleteSmsSignature = (data) => { + return http2.delete('/api/v1/admin/server/sms_service/signature/delete', { data, ...formHeaders }) +} + +export const submitSmsSignature = (data) => { + return http2.post('/api/v1/admin/server/sms_service/signature/submit', data, formHeaders) +} + +export const approveSmsSignature = (data) => { + return http2.post('/api/v1/admin/server/sms_service/signature/approve', data, formHeaders) +} + +export const rejectSmsSignature = (data) => { + return http2.post('/api/v1/admin/server/sms_service/signature/reject', data, formHeaders) +} + +// ========== 短信模板管理 ========== + +export const getSmsTemplateList = (params) => { + return http2.get('/api/v1/admin/server/sms_service/template/list', { params }) +} + +export const getSmsTemplateDetail = (params) => { + return http2.get('/api/v1/admin/server/sms_service/template/detail', { params }) +} + +export const createSmsTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/create', data, formHeaders) +} + +export const updateSmsTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/update', data, formHeaders) +} + +export const deleteSmsTemplate = (data) => { + return http2.delete('/api/v1/admin/server/sms_service/template/delete', { data, ...formHeaders }) +} + +export const submitSmsTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/submit', data, formHeaders) +} + +export const approveSmsTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/approve', data, formHeaders) +} + +export const rejectSmsTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/reject', data, formHeaders) +} + +// ========== 推荐模板管理 ========== + +export const getSmsRecommendedTemplateList = (params) => { + return http2.get('/api/v1/admin/server/sms_service/template/recommended/list', { params }) +} + +export const createSmsRecommendedTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/recommended/create', data, formHeaders) +} + +export const updateSmsRecommendedTemplate = (data) => { + return http2.post('/api/v1/admin/server/sms_service/template/recommended/update', data, formHeaders) +} + +export const deleteSmsRecommendedTemplate = (data) => { + return http2.delete('/api/v1/admin/server/sms_service/template/recommended/delete', { data, ...formHeaders }) +} diff --git a/src/config/menus.js b/src/config/menus.js index b56d24e..76ce3db 100644 --- a/src/config/menus.js +++ b/src/config/menus.js @@ -161,6 +161,14 @@ export const menus = [ { path: '/sms/goods', title: '额度商品管理' + }, + { + path: '/sms/signature', + title: '签名管理' + }, + { + path: '/sms/template', + title: '模板管理' } ] }, diff --git a/src/router/index.js b/src/router/index.js index 6e5d583..3f85106 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -630,6 +630,18 @@ const routes = [ name: 'SmsGoods', component: () => import('../views/sms/SmsGoods.vue'), meta: { title: '额度商品管理' } + }, + { + path: 'signature', + name: 'SmsSignature', + component: () => import('../views/sms/SmsSignature.vue'), + meta: { title: '签名管理' } + }, + { + path: 'template', + name: 'SmsTemplateMgr', + component: () => import('../views/sms/SmsTemplate.vue'), + meta: { title: '模板管理' } } ] }, diff --git a/src/views/marketing/UserVoucher.vue b/src/views/marketing/UserVoucher.vue index f71e2a4..529abf6 100644 --- a/src/views/marketing/UserVoucher.vue +++ b/src/views/marketing/UserVoucher.vue @@ -359,7 +359,7 @@ const editForm = reactive({ const editRules = { discount_id: [ - { required: true, message: '请输入代金券ID', trigger: 'blur' } + { required: false, message: '请输入代金券ID', trigger: 'blur' } ], use_times: [ { required: true, message: '请输入已使用次数', trigger: 'blur' } diff --git a/src/views/order/OrderList.vue b/src/views/order/OrderList.vue index 818b005..dd111e1 100644 --- a/src/views/order/OrderList.vue +++ b/src/views/order/OrderList.vue @@ -73,6 +73,11 @@ + + + @@ -174,8 +179,12 @@ {{ orderDetail.id }} {{ orderDetail.name }} + + {{ getTypeText(orderDetail.type) }} + {{ orderDetail.userId }} {{ orderDetail.commodityId }} + {{ orderDetail.planId || '-' }} {{ orderDetail.table }} {{ orderDetail.payNum }} ¥{{ (orderDetail.price / 100).toFixed(2) }} @@ -198,191 +207,231 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - 待支付 - 已支付 - 已失效 - - - - - - - - - - - -
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
该商品暂无套餐,可直接进入下一步手动设置价格
+
+ + + + + +
+
+ 商品参数配置 +
+ + + + + + +
+ + {{ param.arg_key }} +
+ + +
+
+
+ 加载商品参数中... +
+
+ + + + + + +
+ + +
+
+
+ + +
+ + +
+
+
+
+ + + + + + + + + + + + + + + 待支付 + 已支付 + 已失效 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
@@ -411,7 +460,7 @@ @@ -421,8 +470,9 @@ import { ref, reactive, onMounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' -import { Plus, Delete, Search, Download, Refresh, User, ShoppingCart, Ticket, Money, Close } from '@element-plus/icons-vue' +import { Plus, Delete, Search, Download, Refresh, User, ShoppingCart, Ticket, Money, Close, Setting, Loading } from '@element-plus/icons-vue' import { getOrderList, getOrderDetail, createOrder, updateOrder, deleteOrder, retryOrderHook } from '@/api/admin/order' +import { getProductPlanList, getProductParameterList, getProductParameterDetail } from '@/api/admin/product' import UserListSelector from '@/components/admin/UserListSelector.vue' import ProductSelector from '@/components/admin/ProductSelector.vue' import DiscountCodeSelector from '@/components/admin/DiscountCodeSelector.vue' @@ -448,42 +498,52 @@ const orderForm = reactive({ order_id: undefined, name: '', table: '', + type: 'create', user_id: undefined, commodity_id: 0, + plan_id: null, pay_num: 1, price: 0, renew_price: 0, expire_time: 0, discount_code_id: 0, - coupon_id: 0, + user_coupon_id: 0, state: 0, - pay_type: '', + pay_type: 'default', + payment_order_id: '', args: '', note: '' }) -const orderRules = { - name: [ - { required: true, message: '请输入订单名称', trigger: 'blur' } - ], - table: [ - { required: true, message: '请输入所属表', trigger: 'blur' } - ], - user_id: [ - { required: true, message: '请输入用户ID', trigger: 'blur' }, - { type: 'number', message: '用户ID必须是数字', trigger: 'blur' } - ], - coupon_id: [ - { message: '请输入代金券ID', trigger: 'blur' }, - { type: 'number', message: '代金券ID必须是数字', trigger: 'blur' } - ], - pay_num: [ - { required: true, message: '请输入购买数量', trigger: 'blur' } - ], - price: [ - { required: true, message: '请输入价格', trigger: 'blur' } - ] +// 分步验证规则 +const step1Rules = { + user_id: [{ required: true, message: '请选择用户', trigger: 'change' }], + type: [{ required: true, message: '请选择订单类型', trigger: 'change' }], + name: [{ required: true, message: '请输入订单名称', trigger: 'blur' }] } +const step2Rules = { + commodity_id: [{ required: true, message: '请选择商品', trigger: 'change' }] +} +const step3Rules = { + price: [{ required: true, message: '请输入价格', trigger: 'blur' }], + pay_num: [{ required: true, message: '请输入数量', trigger: 'blur' }] +} + +// 分步向导 +const currentStep = ref(0) +const submitLoading = ref(false) +const step1FormRef = ref(null) +const step2FormRef = ref(null) +const step3FormRef = ref(null) + +// 套餐相关 +const planList = ref([]) +const planLoading = ref(false) + +// 商品参数相关 +const productParams = ref([]) +const paramsLoading = ref(false) +const argValues = reactive({}) // 状态数据 const loading = ref(false) @@ -494,7 +554,6 @@ const selectedRows = ref([]) const dialogVisible = ref(false) const detailDialogVisible = ref(false) const dialogType = ref('add') -const orderFormRef = ref(null) // 选择器弹窗状态 const userSelectorVisible = ref(false) @@ -559,16 +618,17 @@ const getStatusType = (status) => { } // 获取订单状态文本 -// state 0:未支付 1:已支付 2:已失效 const getStatusText = (status) => { - const statusMap = { - 0: '待支付', - 1: '已支付', - 2: '已失效' - } + const statusMap = { 0: '待支付', 1: '已支付', 2: '已失效' } return statusMap[status] || '未知' } +// 获取订单类型文本 +const getTypeText = (type) => { + const typeMap = { create: '新购', renew: '续费', update: '升级', snapshot: '快照', backup: '备份', data_volume: '数据盘', ipv4: 'IPv4', ipv6: 'IPv6' } + return typeMap[type] || type || '-' +} + // 查询 const handleQuery = () => { queryParams.page = 1 @@ -605,26 +665,32 @@ const handleCurrentChange = (page) => { // 新增订单 const handleAdd = () => { dialogType.value = 'add' - dialogVisible.value = true + currentStep.value = 0 clearAllSelections() + planList.value = [] + productParams.value = [] + Object.keys(argValues).forEach(k => delete argValues[k]) Object.assign(orderForm, { order_id: undefined, name: '', - table: '', + table: 'good', + type: 'create', user_id: undefined, commodity_id: 0, + plan_id: null, pay_num: 1, price: 0, renew_price: 0, expire_time: 0, discount_code_id: 0, - coupon_id: 0, + user_coupon_id: 0, state: 0, - pay_type: '', + pay_type: 'default', + payment_order_id: '', args: '', note: '' }) - orderFormRef.value?.resetFields() + dialogVisible.value = true } // 查看订单详情 @@ -644,10 +710,12 @@ const handleView = async (row) => { // 编辑订单 const handleEdit = (row) => { dialogType.value = 'edit' - dialogVisible.value = true + currentStep.value = 0 clearAllSelections() + planList.value = [] + productParams.value = [] + Object.keys(argValues).forEach(k => delete argValues[k]) - // 处理过期时间:优先使用已转换的时间戳,否则转换ISO格式 let expireTimeMs = null if (row._expireTimeMs !== undefined) { expireTimeMs = row._expireTimeMs @@ -658,28 +726,53 @@ const handleEdit = (row) => { Object.assign(orderForm, { order_id: row.id, name: row.name, - table: row.table, + table: row.table || '', + type: row.type || 'create', user_id: row.userId, - commodity_id: row.commodityId, + commodity_id: row.commodityId || 0, + plan_id: row.planId || null, pay_num: row.payNum, price: row.price, renew_price: row.renewPrice, expire_time: expireTimeMs, - discount_code_id: 0, // 从详情接口获取 - coupon_id: 0, // 从详情接口获取 + discount_code_id: 0, + user_coupon_id: 0, state: row.state, - pay_type: row.payType || '', + pay_type: row.payType || 'default', + payment_order_id: row.paymentOrderId || '', args: row.args || '', note: row.note || '' }) - // 设置显示信息(只显示ID,名称需要从选择器中获取) if (row.userId) { selectedUserInfo.value = { user_id: row.userId, user_name: `用户${row.userId}` } } if (row.commodityId) { selectedProductInfo.value = { id: row.commodityId, name: `商品${row.commodityId}` } + fetchPlanList(row.commodityId) + fetchProductParams(row.commodityId).then(() => { + // 从已有 args 中恢复参数值 + if (row.args) { + try { + const existingArgs = JSON.parse(row.args) + if (Array.isArray(existingArgs)) { + for (const a of existingArgs) { + const param = productParams.value.find(p => p.id === a.arg_id) + if (!param) continue + if (param.type === 'select') { + argValues[param.id] = a.attr_id + } else if (param.type === 'number') { + argValues[param.id] = a.number + } else { + argValues[param.id] = a.value || '' + } + } + } + } catch {} + } + }) } + dialogVisible.value = true } // 重试订单流程 @@ -744,64 +837,185 @@ const handleBatchDelete = () => { }).catch(() => {}) } -// 提交表单 -const submitForm = () => { - orderFormRef.value?.validate(async (valid) => { - if (valid) { - try { - // 处理过期时间:将毫秒级时间戳转换为秒级时间戳 - let expireTimeSeconds = 0 - if (orderForm.expire_time) { - const timestamp = timeToTimestamp(new Date(orderForm.expire_time)) - expireTimeSeconds = timestamp || 0 - } - - // 2026-05-12: /api/v1/admin/order/create 与 /update 入参 price / renew_price 单位由 - // "分"改为"元"(后端接口变更,用户确认两端都已生效);列表与表单内部仍按"分"持有, - // 提交时统一除以 100 做"分→元"换算,避免再次入库时被当成元导致额外放大 100 倍。 - // 输入框旁的"分"单位文案暂不改动(用户明确仅要求 /100),UI 一致性问题待后续单独处理。 - const submitData = { - name: orderForm.name, - table: orderForm.table, - user_id: Number(orderForm.user_id), - commodity_id: Number(orderForm.commodity_id), - pay_num: Number(orderForm.pay_num), - price: Number(orderForm.price) / 100, - renew_price: Number(orderForm.renew_price) / 100, - expire_time: expireTimeSeconds, - discount_code_id: Number(orderForm.discount_code_id), - coupon_id: Number(orderForm.coupon_id), - state: Number(orderForm.state), - pay_type: orderForm.pay_type || '', - args: orderForm.args || '', - note: orderForm.note || '' - } - - // 如果是编辑,添加order_id - if (dialogType.value === 'edit') { - submitData.order_id = Number(orderForm.order_id) - } - - console.log('提交订单数据:', submitData) - - let res - if (dialogType.value === 'add') { - res = await createOrder(submitData) - } else { - res = await updateOrder(submitData) - } - - if (res.data.code === 200) { - ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功') - dialogVisible.value = false - fetchOrderList() - } - } catch (error) { - console.error('操作失败:', error) - ElMessage.error(error.response?.data?.message || '操作失败') - } +// 订单类型变更 +const handleTypeChange = (type) => { + if (type === 'create') { + orderForm.table = 'good' + } +} + +// 分步导航 +const handleNextStep = async () => { + if (currentStep.value === 0) { + const formEl = step1FormRef.value + if (!formEl) return + const valid = await formEl.validate().catch(() => false) + if (!valid) return + } else if (currentStep.value === 1) { + const formEl = step2FormRef.value + if (!formEl) return + const valid = await formEl.validate().catch(() => false) + if (!valid) return + } + currentStep.value++ +} + +// 加载套餐列表 +const fetchPlanList = async (goodId) => { + if (!goodId) { planList.value = []; return } + planLoading.value = true + try { + const res = await getProductPlanList({ good_id: goodId, page: 1, count: 100 }) + if (res.data.code === 200) { + planList.value = res.data.data?.list || res.data.data || [] + } else { + planList.value = [] } - }) + } catch { planList.value = [] } + finally { planLoading.value = false } +} + +// 加载商品参数列表 +const fetchProductParams = async (goodId) => { + if (!goodId) { productParams.value = []; return } + paramsLoading.value = true + try { + const res = await getProductParameterList({ good_id: goodId }) + if (res.data.code === 200) { + const list = res.data.data || [] + await Promise.all(list.map(async (param) => { + try { + const detail = await getProductParameterDetail({ good_id: goodId, arg_id: param.id }) + if (detail.data.code === 200) { + const attrs = detail.data.data.attrs || [] + attrs.sort((a, b) => (Number(a.index) || 0) - (Number(b.index) || 0)) + param.attrs = attrs + } else { param.attrs = [] } + } catch { param.attrs = [] } + })) + productParams.value = list + // 初始化默认值 + for (const param of list) { + if (argValues[param.id] !== undefined) continue + if (param.type === 'select' && param.attrs?.length) { + argValues[param.id] = param.attrs[0].id + } else if (param.type === 'number') { + argValues[param.id] = param.min || 0 + } else { + argValues[param.id] = '' + } + } + } else { productParams.value = [] } + } catch { productParams.value = [] } + finally { paramsLoading.value = false } +} + +// 根据参数表单值构建 args JSON +const buildArgsJson = () => { + if (!productParams.value.length) return '' + const args = [] + for (const param of productParams.value) { + const val = argValues[param.id] + if (val === undefined || val === null || val === '') continue + if (param.type === 'select') { + const attr = param.attrs?.find(a => a.id === val) + if (attr) { + args.push({ arg_id: param.id, name: param.name, attr_id: attr.id, value: attr.value || attr.name }) + } + } else if (param.type === 'number') { + const matchedAttr = findMatchedAttr(param, val) + args.push({ arg_id: param.id, name: param.name, attr_id: matchedAttr?.id || (param.attrs?.[0]?.id || 0), number: val }) + } else { + const attr = param.attrs?.[0] + args.push({ arg_id: param.id, name: param.name, attr_id: attr?.id || 0, value: val }) + } + } + return args.length > 0 ? JSON.stringify(args) : '' +} + +const findMatchedAttr = (param, numValue) => { + if (!param.attrs || !param.attrs.length) return null + const sorted = [...param.attrs].sort((a, b) => (a.phase || 0) - (b.phase || 0)) + for (const attr of sorted) { + const phase = attr.phase || 0 + if (attr.rangeType === 'before' && numValue <= phase) return attr + if (attr.rangeType === 'after' && numValue >= phase) return attr + if (attr.rangeType === 'equal' && numValue === phase) return attr + } + return sorted[sorted.length - 1] +} + +// 套餐选择变更 +const handlePlanChange = (planId) => { + if (!planId) return + const plan = planList.value.find(p => p.id === planId) + if (plan) { + if (plan.price) orderForm.price = plan.price + if (plan.renew_price) orderForm.renew_price = plan.renew_price + } +} + +// 提交表单 +const submitForm = async () => { + const formEl = step3FormRef.value + if (formEl) { + const valid = await formEl.validate().catch(() => false) + if (!valid) return + } + + submitLoading.value = true + try { + let expireTimeStr = '' + if (orderForm.expire_time) { + expireTimeStr = new Date(Number(orderForm.expire_time)).toISOString() + } + + const argsStr = productParams.value.length ? buildArgsJson() : orderForm.args || '' + + const submitData = { + name: orderForm.name, + table: orderForm.table || '', + type: orderForm.type, + user_id: Number(orderForm.user_id), + commodity_id: Number(orderForm.commodity_id), + pay_num: Number(orderForm.pay_num), + price: Number(orderForm.price) / 100, + renew_price: Number(orderForm.renew_price) / 100, + expire_time: expireTimeStr, + state: Number(orderForm.state), + pay_type: orderForm.pay_type || 'default', + args: argsStr, + note: orderForm.note || '' + } + + if (orderForm.plan_id) submitData.plan_id = Number(orderForm.plan_id) + if (orderForm.discount_code_id) submitData.discount_code_id = Number(orderForm.discount_code_id) + if (orderForm.user_coupon_id) submitData.user_coupon_id = Number(orderForm.user_coupon_id) + if (orderForm.payment_order_id) submitData.payment_order_id = orderForm.payment_order_id + + if (dialogType.value === 'edit') { + submitData.order_id = Number(orderForm.order_id) + } + + let res + if (dialogType.value === 'add') { + res = await createOrder(submitData) + } else { + res = await updateOrder(submitData) + } + + if (res.data.code === 200) { + ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功') + dialogVisible.value = false + fetchOrderList() + } else { + ElMessage.error(res.data.message || '操作失败') + } + } catch (error) { + ElMessage.error(error.response?.data?.message || '操作失败') + } finally { + submitLoading.value = false + } } // 用户选择处理 @@ -819,15 +1033,21 @@ const clearUser = () => { const handleProductSelect = (product) => { orderForm.commodity_id = product.id selectedProductInfo.value = product - // 自动填充表名 - if (product.table) { - orderForm.table = product.table - } + if (product.table) orderForm.table = product.table + orderForm.plan_id = null + // 清空旧参数值 + Object.keys(argValues).forEach(k => delete argValues[k]) + fetchPlanList(product.id) + fetchProductParams(product.id) } const clearProduct = () => { orderForm.commodity_id = 0 + orderForm.plan_id = null selectedProductInfo.value = null + planList.value = [] + productParams.value = [] + Object.keys(argValues).forEach(k => delete argValues[k]) } // 优惠码选择处理 @@ -843,12 +1063,12 @@ const clearDiscountCode = () => { // 代金券选择处理 const handleVoucherSelect = (voucher) => { - orderForm.coupon_id = voucher.id + orderForm.user_coupon_id = voucher.id selectedVoucherInfo.value = voucher } const clearVoucher = () => { - orderForm.coupon_id = 0 + orderForm.user_coupon_id = 0 selectedVoucherInfo.value = null } @@ -946,11 +1166,57 @@ onMounted(() => { justify-content: flex-end; } -.dialog-footer { +/* 分步向导样式 */ +.wizard-container { + padding: 0 10px; +} + +.wizard-steps { + margin-bottom: 30px; +} + +.wizard-body { + min-height: 280px; +} + +.wizard-footer { display: flex; justify-content: flex-end; gap: 12px; - padding: 0; +} + +.form-tip { + font-size: 12px; + color: #909399; + margin-top: 4px; + line-height: 1.4; +} + +.args-section { + margin-top: 16px; + padding: 16px; + background: #f9fafb; + border-radius: 8px; + border: 1px solid #ebeef5; +} + +.args-section-title { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 600; + color: #303133; + margin-bottom: 16px; + padding-bottom: 10px; + border-bottom: 1px solid #ebeef5; +} + +.number-arg-row { + display: flex; + align-items: center; + gap: 8px; + width: 100%; } /* 表格样式优化 */ diff --git a/src/views/product/ProductList.vue b/src/views/product/ProductList.vue index d59c2fc..b9f4e00 100644 --- a/src/views/product/ProductList.vue +++ b/src/views/product/ProductList.vue @@ -488,11 +488,11 @@
数值范围配置
- - + + -
before: 数值 < phase 时匹配 | after: 数值 > phase 时匹配
+
before: 数值 ≤ phase 时匹配 | after: 数值 ≥ phase 时匹配
@@ -1532,7 +1532,7 @@ const getArgTypeTag = (type) => { // 范围类型显示 const getRangeTypeText = (type) => { - const typeMap = { 'after': '大于 >', 'before': '小于 <', 'equal': '等于 =' } + const typeMap = { 'after': '大于等于 ≥', 'before': '小于等于 ≤', 'equal': '等于 =' } return typeMap[type] || type || '-' } @@ -1960,9 +1960,9 @@ const findMatchingNumberAttr = (spec, numValue) => { const phase = attr.phase || 0 const rangeType = attr.rangeType || 'before' - if (rangeType === 'before' && numValue < phase) { + if (rangeType === 'before' && numValue <= phase) { return attr - } else if (rangeType === 'after' && numValue > phase) { + } else if (rangeType === 'after' && numValue >= phase) { return attr } else if (rangeType === 'equal' && numValue === phase) { return attr diff --git a/src/views/product/components/ProductParameterManager.vue b/src/views/product/components/ProductParameterManager.vue index 231f658..9b8d38d 100644 --- a/src/views/product/components/ProductParameterManager.vue +++ b/src/views/product/components/ProductParameterManager.vue @@ -275,13 +275,13 @@
- - - 小于 + + + 小于等于 - - - 大于 + + + 大于等于 @@ -635,7 +635,7 @@ const getArgTypeTag = (type) => { return tagMap[type] || 'info' } const getRangeTypeText = (type) => { - const typeMap = { 'after': '>', 'before': '<', 'equal': '=' } + const typeMap = { 'after': '≥', 'before': '≤', 'equal': '=' } return typeMap[type] || type || '-' } diff --git a/src/views/product/components/ProductPlanManager.vue b/src/views/product/components/ProductPlanManager.vue index b07d3ac..45c1cf8 100644 --- a/src/views/product/components/ProductPlanManager.vue +++ b/src/views/product/components/ProductPlanManager.vue @@ -548,8 +548,8 @@ const findMatchingNumberAttr = (spec, numValue) => { for (const attr of sortedAttrs) { const phase = attr.phase || 0 const rangeType = attr.rangeType || 'before' - if (rangeType === 'before' && numValue < phase) return attr - else if (rangeType === 'after' && numValue > phase) return attr + if (rangeType === 'before' && numValue <= phase) return attr + else if (rangeType === 'after' && numValue >= phase) return attr else if (rangeType === 'equal' && numValue === phase) return attr } return sortedAttrs[sortedAttrs.length - 1] diff --git a/src/views/sms/SmsService.vue b/src/views/sms/SmsService.vue index 83057e0..9b354cc 100644 --- a/src/views/sms/SmsService.vue +++ b/src/views/sms/SmsService.vue @@ -46,6 +46,7 @@ {{ row.name }} + 默认
@@ -73,13 +74,15 @@ - + @@ -137,7 +140,8 @@ import { getSmsServiceList, createSmsService, updateSmsService, - deleteSmsService + deleteSmsService, + setDefaultSmsService } from '@/api/admin/smsService.js' const router = useRouter() @@ -270,6 +274,28 @@ const handleDelete = async (row) => { } } +const handleSetDefault = async (row) => { + try { + const params = new URLSearchParams() + params.append('id', row.id) + const res = await setDefaultSmsService(params) + if (res.data.code === 200) { + ElMessage.success(`已将「${row.name}」设为默认短信服务`) + fetchList() + } else { + ElMessage.error(res.data.message || '设置失败') + } + } catch (e) { + ElMessage.error('设置默认服务失败') + } +} + +const openConsole = (row) => { + const base = (row.host || '').replace(/\/+$/, '') + if (!base) return ElMessage.warning('该服务未配置地址') + window.open(`${base}/login?serverToken=${encodeURIComponent(row.serviceToken || '')}`, '_blank') +} + const goToGoods = (row) => { router.push({ path: '/sms/goods', query: { service_id: row.id, service_name: row.name } }) } diff --git a/src/views/sms/SmsSignature.vue b/src/views/sms/SmsSignature.vue new file mode 100644 index 0000000..2e24732 --- /dev/null +++ b/src/views/sms/SmsSignature.vue @@ -0,0 +1,549 @@ + + + + + diff --git a/src/views/sms/SmsTemplate.vue b/src/views/sms/SmsTemplate.vue new file mode 100644 index 0000000..6aee72b --- /dev/null +++ b/src/views/sms/SmsTemplate.vue @@ -0,0 +1,632 @@ + + + + + diff --git a/src/views/system/NoticeChannel.vue b/src/views/system/NoticeChannel.vue index e2e56a3..a13fca8 100644 --- a/src/views/system/NoticeChannel.vue +++ b/src/views/system/NoticeChannel.vue @@ -193,43 +193,75 @@ - - - - - - - - - - - - - - 邮件 - - - - - - 短信 - - - - - -
- - - {{ arg }} + +
+ +
+ + + + + + + + + + + + + 邮件 + + + + + + 短信 + + + + + +
+ + + {{ arg }} + +
+
点击参数按钮可将其插入到模板内容光标处
+
+ + + +
+
+ + +
+
+ + + 渲染预览 + + + 刷新
-
点击参数按钮可将其插入到模板内容光标处
- - - - - + +
+ +
+ +

{{ previewError }}

+
+
+ +

填写模板标识和类型后自动加载预览

+
+
+
+