diff --git a/src/assets/images/loadingFaild.png b/src/assets/images/loadingFaild.png new file mode 100644 index 0000000..ee52460 Binary files /dev/null and b/src/assets/images/loadingFaild.png differ diff --git a/src/components/locales/lang/en.js b/src/components/locales/lang/en.js index 4419fed..4f8bafa 100644 --- a/src/components/locales/lang/en.js +++ b/src/components/locales/lang/en.js @@ -185,6 +185,8 @@ export default { // Message Group elmessage: { // Common + accountEmpty: "Account cannot be empty", + passwordEmpty: "Password cannot be empty", checkRefundgolds: "Refund gold cannot be empty", checkPermanentGold: "Permanent gold cannot be empty", loginSuccess: "Login successful", @@ -553,6 +555,7 @@ export default { // Add Form Fields common_add: { + orderStatus: "Order Status", jwcode: "Homily ID", jwcodePlaceholder: "Enter Homily ID", originalChinese: "Chinese", @@ -666,10 +669,13 @@ export default { operationConfirm: "Confirm", userInfo: "User Info", prompt: "Risk Alert", + promptConsume: "Risk Alert for Consume", similarRechargeRecords: "Similar recent records detected", + similarCosumeRecords: "Similar recent consume records detected", rechargePermanentGold: "Recharge Perm Coins", buy: "Buy", operator: "Operator", + orderStatus: "Order Status", submitter: "Submitter", continueOperation: "Continue?", // Gold Bean @@ -794,6 +800,10 @@ export default { unknown: "Unknown", // 金币新增充值 addCoinRecharge: "New Recharge", + waitAudit: "Pending", + refunded: "Refunded", + passed: "Passed", + rejected: "Rejected", // // Pay Methods payMethods: { @@ -1018,7 +1028,7 @@ export default { market: "Region", registerTime: "Registered at", }, - orderNeedsReview: "A receipt order requires review", + // orderNeedsReview: "A receipt order requires review", messageGroups: { today: "Today", yesterday: "Yesterday", @@ -1089,6 +1099,8 @@ export default { ipay88: "Ipay88", paymentAsia: "PaymentAsia", transfer: "E-Transfer", + paysolution:'paysolution', + stripe2:'Stripe2', }, statusList: { submitted: "Submitted", diff --git a/src/components/locales/lang/zh-CN.js b/src/components/locales/lang/zh-CN.js index f285323..fd2eba9 100644 --- a/src/components/locales/lang/zh-CN.js +++ b/src/components/locales/lang/zh-CN.js @@ -185,6 +185,8 @@ export default { // 提示信息组 elmessage: { // 通用 + accountEmpty: "账号不能为空", + passwordEmpty: "密码不能为空", checkRefundgolds: "退款金币总数不能为0", checkPermanentGold: "永久金币不能为空", loginSuccess: "登录成功", @@ -624,6 +626,7 @@ export default { receiveArea: "到账地区", receiveAreaPlaceholder: "请选择到账地区", price: "原价", + orderStatus: "订单状态", goodsNamePlaceholder: "请选择商品", 使用红包: "使用红包", 不使用红包: "不使用红包", @@ -666,9 +669,12 @@ export default { operationConfirm: "操作确认", userInfo: "用户信息", prompt: "重复充值风险提示", + promptConsume: "重复消耗风险提示", similarRechargeRecords: "检测到该用户近期有相似充值记录", + similarCosumeRecords: "检测到该用户近期有相似消耗记录", rechargePermanentGold: "充值永久金币", buy: "购买", + orderStatus:'订单状态', operator: "操作人", submitter: "提交人", continueOperation: "是否继续操作?", @@ -794,6 +800,10 @@ export default { unknown: "未知状态", // 金币新增充值 --------------------------------- addCoinRecharge: "新增充值", + waitAudit: "待审核", + refunded: "已退款", + passed: "已通过", + rejected: "已驳回", // 支付方式 // 添加支付方式 payMethods: { @@ -1029,7 +1039,7 @@ export default { market: "地区", registerTime: "注册时间", }, - orderNeedsReview: "用户有条收款订单需审核", + // orderNeedsReview: "用户有条收款订单需审核", messageGroups: { today: "今天", yesterday: "昨天", @@ -1101,6 +1111,8 @@ export default { ipay88: "Ipay88", paymentAsia: "PaymentAsia", transfer: "E-Transfer", + paysolution:'paysolution', + stripe2:'Stripe2', }, statusList: { submitted: "已提交", diff --git a/src/router/index.js b/src/router/index.js index b0a927a..d739a6f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -307,27 +307,27 @@ const routes = [ { path: 'receiveDetail', name: "receiveDetail", - meta: {permissionId: 74}, + meta: {permissionId: 59}, children: [ // 收款提交 { path: 'receiveService', name: "receiveService", component: () => import("../views/moneyManage/receiveDetail/receiveService.vue"), - meta: {permissionId: 60} + meta: {permissionId: [60, 61, 62, 63, 64, 65, 66]} }, // 收款处理(负责人) { path: 'receiveManager', name: "receiveManager", component: () => import("../views/moneyManage/receiveDetail/receiveManage.vue"), - meta: {permissionId: [67, 79]} + meta: {permissionId: [79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90]} }, {//收款处理 path: 'receiveFinance', name: "receiveFinance", component: () => import("../views/moneyManage/receiveDetail/receiveFinance.vue"), - meta: {permissionId: [67, 79]} + meta: {permissionId: [67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78]} }, //总部管理员及财务(移除) // { @@ -350,28 +350,28 @@ const routes = [ path: 'refundService', name: "refundService", component: () => import("../views/moneyManage/refundDetail/refundService.vue"), - meta: {permissionId: 99} + meta: {permissionId: [99, 100, 101, 102]} }, // 退款审批 { path: 'refundFinance', name: "refundFinance", component: () => import("../views/moneyManage/refundDetail/refundFinance.vue"), - meta: {permissionId: 103} + meta: {permissionId: [103, 104, 105, 106]} }, // 退款审批(负责人) { path: 'refundCharge', name: "refundCharge", component: () => import("../views/moneyManage/refundDetail/refundCharge.vue"), - meta: {permissionId: 107} + meta: {permissionId: [107, 108, 109, 110]} }, // 退款审批(总部财务) { path: 'refundHeader', name: "refundHeader", component: () => import("../views/moneyManage/refundDetail/refundHeader.vue"), - meta: {permissionId: 111} + meta: {permissionId: [111, 112, 113, 114]} }, ] }, @@ -380,7 +380,7 @@ const routes = [ path: 'executor', name: "executor", component: () => import("../views/moneyManage/executor/executor.vue"), - meta: {permissionId: 115} + meta: {permissionId: [115, 116, 117, 118]} }, // 财务核算 { @@ -579,11 +579,12 @@ router.afterEach(async (to) => { // 接收to参数获取当前路由信息 // 存入全局状态,供所有页面访问 const messageStore = useMessageStore(); // 过滤 flag=1的消息 - newMessageRes.data = newMessageRes.data.filter(item => item.flag !== 1); - - messageStore.setMessages(newMessageRes.data); + const list = Array.isArray(newMessageRes.data) + ? newMessageRes.data + : (Array.isArray(newMessageRes.data?.list) ? newMessageRes.data.list : []); + messageStore.setMessages(list.filter(item => item.flag !== 1)); } catch (error) { console.error('获取消息失败:', error); } }); -export default router; \ No newline at end of file +export default router; diff --git a/src/util/http.js b/src/util/http.js index f5dc608..c3052fa 100644 --- a/src/util/http.js +++ b/src/util/http.js @@ -1,14 +1,15 @@ import request from './request' export default function(options) { - const { method = 'post', url, data = {}, params = {}, headers = {} } = options + const { method = 'post', url, data = {}, params = {}, headers = {}, responseType } = options return request({ method, url, data, params, - headers + headers, + responseType }) .then(({ status, data, statusText }) => { if (status === 200) { diff --git a/src/utils/getMessage.js b/src/utils/getMessage.js index 576d845..839a85c 100644 --- a/src/utils/getMessage.js +++ b/src/utils/getMessage.js @@ -15,7 +15,10 @@ function formatTime(timeStr) { // 函数逻辑不变... const now = new Date(); const msgTime = new Date(timeStr); - const diffMs = now - msgTime; + let diffMs = now - msgTime; + if (diffMs < 0) { + diffMs = 0 + } const diffMins = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); diff --git a/src/utils/goToCheck.js b/src/utils/goToCheck.js index c9d40b6..e67f6ef 100644 --- a/src/utils/goToCheck.js +++ b/src/utils/goToCheck.js @@ -33,4 +33,44 @@ export function getOrderPage(status) { } // 未知状态返回工作台 return '/workbench'; -} \ No newline at end of file +} + +function toNumberOrNull(value) { + if (value === null || value === undefined) return null; + const num = typeof value === 'number' ? value : Number(String(value).trim()); + return Number.isFinite(num) ? num : null; +} + +const queryIdRouteNameMap = { + 6: 'rechargeAudit', + 7: 'rechargeAudit', + 8: 'rechargeAudit', + 9: 'rechargeAudit', + 10: 'rechargeAudit', + 11: 'rechargeAudit', + 12: 'rechargeAudit', + 13: 'refundAudit', + 14: 'refundAudit', + 15: 'refundAudit', + 16: 'refundAudit', + 17: 'refundAudit', + 18: 'refundAudit', + 19: 'refundAudit' +}; + +export function getMessageJumpTarget(message) { + const permissionId = toNumberOrNull(message?.queryId ?? message?.menuId ?? message?.permissionId); + if (permissionId !== null) { + const routeName = queryIdRouteNameMap[permissionId]; + if (routeName) return { name: routeName }; + } + + const type = toNumberOrNull(message?.type); + if (type === 0) return { name: 'rechargeAudit' }; + if (type === 1) return { name: 'refundAudit' }; + + const status = toNumberOrNull(message?.status); + if (status !== null) return getOrderPage(status); + + return '/workbench'; +} diff --git a/src/views/consume/gold/addCoinConsume.vue b/src/views/consume/gold/addCoinConsume.vue index 6b9816d..d6474c6 100644 --- a/src/views/consume/gold/addCoinConsume.vue +++ b/src/views/consume/gold/addCoinConsume.vue @@ -264,11 +264,12 @@ function calculateCoins(sumGold) { // 用来写的 cookie 的 key const WriteCookies = ref(null) // 用来写的 cookie 的 value -const WriteCookiesTime = ref(null) +const WriteCookiesVale = ref({}) // 用来读的 cookie 的 key const ReadCookies = ref(null) // 用来读的 cookie 的 value -const ReadCookiesTime = ref(null) +const ReadCookiesValue = ref({}) +const orderStatus = ref('') // 这是添加消费信息的接口 @@ -298,7 +299,7 @@ const add = async function () { freeGold: addConsume.value.freeGold * 100, taskGold: addConsume.value.taskGold * 100, permanentGold: addConsume.value.permanentGold * 100, - goodsName: addConsume.value.goodsName.value, + goodsName: addConsume.value.goodsName?.value || addConsume.value.goodsName, remark: addConsume.value.remark, adminName: adminData.value.adminName, redMoney: Number(addConsume.value.redMoney), // 1-使用红包,0-不使用红包 @@ -322,16 +323,15 @@ const add = async function () { function handleResponse(result) { console.log("响应结果", result) if (result.code === 200) { - //存一下 用户的jwcode - // 拼接 jwcode:permanentGold:freeGold - WriteCookies.value = `coinConsume:${addConsume.value.jwcode}:${addConsume.value.goodsName.value}` - //value 为当前消耗时间 - WriteCookiesTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss"); - // 设置cookies,用户jwcode为key,value也是jwcode,过期时间为1天 - Cookies.set(WriteCookies.value, WriteCookiesTime.value, { - expires: - 1, path: '/' - }); + const goodsNameStr = addConsume.value.goodsName?.value || addConsume.value.goodsName + WriteCookies.value = `coinConsume-${addConsume.value.jwcode}-${goodsNameStr}` + //value 为消耗时间 + console.log("goodsNameStr add", goodsNameStr) + WriteCookiesVale.value = JSON.stringify({ + payTime: moment(result.data.createTime).format("YYYY-MM-DD HH:mm:ss"), + orderCode: result.data.orderCoder, + }); + Cookies.set(WriteCookies.value, WriteCookiesVale.value, {expires: 1, path: '/'}); ElMessage.success(t('elmessage.addSuccess')); console.log("请求成功", result); } else { @@ -448,6 +448,26 @@ const FirstConsumeDialogVisibleCancel = () => { // }); // }; + +const historyOrder = ref({}) +// 查询订单号对应的状态 +const selectByOrderCode = async function (data) { + try { + const result = await request({ + url: '/recharge/selectByOrderCode', + data: data, + headers: { + 'Content-Type': 'text/plain;charset=utf-8' + }, + }) + if(result.code == 200){ + historyOrder.value = result.data + } + } catch (error) { + console.log('请求失败', error) + // 在这里可以处理错误逻辑,比如显示错误提示等 + } +} // 添加前验证 const addBefore = () => { Ref.value.validate(async (valid) => { @@ -462,16 +482,28 @@ const addBefore = () => { if (!validateInput() || !validateRedLimit()) { return; } - ReadCookies.value = `coinConsume:${addConsume.value.jwcode}:${addConsume.value.goodsName.value}` + ReadCookies.value = `coinConsume-${addConsume.value.jwcode}-${addConsume.value.goodsName.value}` + console.log('ReadCookies',ReadCookies.value); // 获取cookie - const cookie = Cookies.get(ReadCookies.value) - console.log("time", WriteCookiesTime.value) - // 格式化时间 - ReadCookiesTime.value = moment(cookie).format('YYYY-MM-DD HH:mm:ss') - console.log("cookie========", cookie) - if (cookie) { + const cookieValue = Cookies.get(ReadCookies.value); + if (cookieValue) { + // 解析为对象 + const rechargeInfo = JSON.parse(cookieValue); + console.log('提交时间:', rechargeInfo.payTime); + console.log('订单号:', rechargeInfo.orderCode); + ReadCookiesValue.value = { + ...rechargeInfo, + } + }else{ + ReadCookiesValue.value = {} + } + console.log('ReadCookies',ReadCookiesValue.value); + if (Object.keys(ReadCookiesValue.value).length > 0) { + await selectByOrderCode(ReadCookiesValue.value.orderCode) + orderStatus.value = historyOrder.value.isRefundDesc + ReadCookiesValue.value.goodsName = historyOrder.value.goodsName ConsumeDialogVisible.value = true; - } else { + } else { FirstConsumeDialogVisible.value = true; } }); @@ -606,16 +638,26 @@ watch( ); // 监听商品选择,自动展示原价 -watch( - () => addConsume.value.goodsName, - (newGoods) => { - if (newGoods && typeof newGoods === 'object') { - addConsume.value.price = Number(newGoods.price || 0) || null; - } else { - addConsume.value.price = null; - } +const handleGoodsChange = (val) => { + // val 现在是整个对象 + if (val) { + addConsume.value.price = Number(val.price || 0) || null; + } else { + addConsume.value.price = null; } -); +} + +// 监听商品选择,自动展示原价 +// watch( +// () => addConsume.value.goodsName, +// (newGoods) => { +// if (newGoods && typeof newGoods === 'object') { +// addConsume.value.price = Number(newGoods.price || 0) || null; +// } else { +// addConsume.value.price = null; +// } +// } +// ); /* ====================红包逻辑================================= @@ -744,9 +786,8 @@ onMounted(async function () { - - + style="width: 200px" clearable filterable @change="handleGoodsChange" value-key="id"> + @@ -1006,13 +1047,13 @@ onMounted(async function () { -

{{ $t('common_add.prompt') }}

+

{{ $t('common_add.promptConsume') }}

-

{{ $t('common_add.similarRechargeRecords') }}

- · {{ ReadCookiesTime }} {{ $t('common_add.buy') }} 【{{ addConsume.goodsName.value }}】({{ +

{{ $t('common_add.similarCosumeRecords') }}

+ · {{ ReadCookiesValue.payTime }} {{ $t('common_add.buy') }} 【{{ ReadCookiesValue.goodsName }}】【{{ $t('common_add.orderStatus') }}: {{ orderStatus }}】({{ $t('common_add.operator') }}: {{ adminData.adminName }})
diff --git a/src/views/home.vue b/src/views/home.vue index 650e8d6..d6421b6 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -19,6 +19,7 @@ import {findMenuById,permissionMapping} from "@/utils/menuTreePermission.js" import {useMessageStore} from '@/store/index.js' // 国际化 import { useI18n } from 'vue-i18n' +import { has } from 'lodash' const {t} = useI18n(); // ------------------ ICONS ------------------ @@ -158,15 +159,19 @@ const selectStatusById = () => { status.push(0); } + //地区财务审核 + if (hasPermission(permissionMapping.area_finance_collection_approved)) { + status.push(1); + } + //和地区负责人审核 + if (hasPermission(permissionMapping.area_manager_collection_approved)) { + status.push(7); + } + //地区客服收款 if (hasPermission(permissionMapping.collection_area_submit)) { status.push(2); } - // 地区负责人收款待审核 - // else if (hasPermission(permissionMapping.area_manager_collection_pending)) { - // status.push(0); - // } - // ===== 退款流程状态 ===== // 地区财务退款审核 @@ -211,7 +216,8 @@ const getMessage = async () => { }); if (res?.data) { - const cleanList = res.data.filter(i => i.flag !== 1) + const list = Array.isArray(res.data) ? res.data : (Array.isArray(res.data?.list) ? res.data.list : []) + const cleanList = list.filter(i => i.flag !== 1) messageStore.setMessages(cleanList) } } catch (e) { @@ -282,6 +288,27 @@ const toggleShowAll = () => showAll.value = !showAll.value const scrollContainer = ref(null) const scrollToTop = () => scrollContainer.value?.scrollTo({top: 0, behavior: 'smooth'}) +const getPathByQueryId = (queryId) => { + const qid = Number(queryId) + if (!Number.isFinite(qid)) return null + + const matchedRoutes = router.getRoutes().filter(r => { + const pid = r.meta?.permissionId + if (Array.isArray(pid)) return pid.includes(qid) + return pid === qid + }) + + if (!matchedRoutes.length) return null + + matchedRoutes.sort((a, b) => { + const aDepth = typeof a.path === 'string' ? a.path.split('/').length : 0 + const bDepth = typeof b.path === 'string' ? b.path.split('/').length : 0 + return bDepth - aDepth + }) + + return matchedRoutes[0]?.path || null +} + // 点击消息 → 已读 + 跳转 const handleMessageClick = async (item) => { const res = await API({ @@ -292,7 +319,22 @@ const handleMessageClick = async (item) => { if (res.code === 200) { closeMessageDialog() - await router.push(getOrderPage(item.status)) + const targetPath = item?.queryId ? getPathByQueryId(item.queryId) : null + const messageStatus = Number(item?.status) + + // 1是代表收款处理的已通过,7是代表的收款处理(负责人)的已通过 + let tab = null + if (messageStatus === 1 && targetPath?.includes('/moneyManage/receiveDetail/receiveFinance')) { + tab = 'pass' + } else if (messageStatus === 7 && targetPath?.includes('/moneyManage/receiveDetail/receiveManager')) { + tab = 'pass' + } + + if (targetPath && tab) { + await router.push({ path: targetPath, query: { tab } }) + } else { + await router.push(targetPath || getOrderPage(item.status) || '/noPermission') + } await getMessage() ElMessage.success(t('elmessage.jumpSuccess')) } else { @@ -456,7 +498,7 @@ onMounted(() => getMessage()) - +
getMessage())
-
[{{ item.marketName }}]
-
[{{item.name}}{{ item.jwcode }}]{{ t('home.orderNeedsReview') }}
+
[{{ item.marketName }}] +
+
[{{item.name}}{{ item.jwcode }}]{{ item.desc}}
-import {onMounted, ref} from 'vue' +import {onMounted, ref,onUnmounted} from 'vue' import {ElMessage} from 'element-plus' import request from '@/util/http' import {useRouter} from 'vue-router' @@ -34,7 +34,78 @@ function getMachineId() { } } -const form = ref({account: null, password: '', token: '', machineId: machineId1.value}) +const form = ref({account: null, password: '', token: '', machineId: machineId1.value, captcha: '', uuid: ''}) +const formRef = ref(null) +const rules = { + account: [ + { required: true, message: '请输入账号', trigger: 'blur' } + ], + password: [ + { required: true, message: '请输入密码', trigger: 'blur' } + ], + captcha: [ + { required: true, message: '请输入验证码', trigger: 'blur' } + ] +} +const captchaUrl = ref('') +const isCaptchaCooldown = ref(false) +let captchaCooldownTimer = null +let cooldownStartTime = 0 // 冷却开始时间(时间戳) +const COOLDOWN_TOTAL = 5 * 1000 // 总冷却时长:5 秒(转毫秒) + +// 启动验证码冷却定时器 +const startCaptchaCooldown = () => { + isCaptchaCooldown.value = true + cooldownStartTime = Date.now() + captchaCooldownTimer = setTimeout(() => { + isCaptchaCooldown.value = false // 冷却结束 + cooldownStartTime = 0 // 重置开始时间 + }, COOLDOWN_TOTAL) +} + +const getCaptchaRemainingSeconds = () => { + if (!isCaptchaCooldown.value || cooldownStartTime === 0) { + return 0 + } + const elapsed = Date.now() - cooldownStartTime + const remainingMs = Math.max(COOLDOWN_TOTAL - elapsed, 0) + const remainingSeconds = Math.ceil(remainingMs / 1000) + return remainingSeconds +} + +const clearCaptchaCooldown = () => { + if (captchaCooldownTimer) { + clearTimeout(captchaCooldownTimer) + captchaCooldownTimer = null + } + isCaptchaCooldown.value = false + cooldownStartTime = 0 +} +// 获取验证码 +const getCaptcha = async () => { + if (isCaptchaCooldown.value) { + ElMessage.warning('验证码获取太频繁,请' + getCaptchaRemainingSeconds() + '秒后再试') + return + } + + try { + let uuid = Date.now() + localStorage.setItem('uuid', uuid) + const res = await request({ + url: '/captcha' + '?uuid=' + uuid, + method: 'get', + responseType: 'blob', + }) + if (captchaUrl.value) { + URL.revokeObjectURL(captchaUrl.value); + } + captchaUrl.value = URL.createObjectURL(res); + + startCaptchaCooldown() + } catch (error) { + console.error('获取验证码失败', error) + } +} const adminRoleId = ref(null) @@ -42,6 +113,12 @@ const adminRoleId = ref(null) const adminStore = useAdminStore() //调用方法 const login = async function () { + if (!formRef.value) return + try { + await formRef.value.validate() + } catch (err) { + return + } if(loading.value) { console.log('正在登录,请稍后') @@ -51,11 +128,15 @@ const login = async function () { loading.value = true try { + let params = { + ...form.value, + uuid: localStorage.getItem('uuid') + } const result = await request({ url: '/admin/login', - data: form.value + data: params }) - console.log('传给后端的参数', form.value) + console.log('传给后端的参数', params) if (result.code === 200) { // 本地存储token @@ -64,7 +145,7 @@ const login = async function () { // 存储用户信息 adminStore.setAdminData(result.data) - + // 本页面使用 adminRoleId adminRoleId.value = result.data.roleId @@ -97,8 +178,10 @@ const login = async function () { } else { form.value.password = '' form.value.account = '' + form.value.captcha = '' ElMessage.error(result.msg) loading.value = false //登录失败时重置loading状态 + getCaptcha() } } catch (error) { console.log('请求失败', error) @@ -161,16 +244,22 @@ const selectMarket = async function () { onMounted(() => { getMachineId() + getCaptcha() +}) +onUnmounted(() => { + clearCaptchaCooldown() })