diff --git a/package-lock.json b/package-lock.json index d539710..a8ba3c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "axios": "^1.13.5", "echarts": "^6.0.0", "element-plus": "^2.13.2", - "goeasy": "^2.14.9", + "goeasy": "2.13.24", "lodash": "^4.18.1", "mitt": "^3.0.1", "sortablejs": "^1.15.7", @@ -1500,9 +1500,9 @@ } }, "node_modules/goeasy": { - "version": "2.14.9", - "resolved": "https://registry.npmjs.org/goeasy/-/goeasy-2.14.9.tgz", - "integrity": "sha512-AlHx7PCWfIUWKAvjILJx6AzM1GmquGM1MxxA7gPH6TiR9eTtiieJNk9nIXvVYRm0iTC8AfEnekRY2Clghm9xrA==" + "version": "2.13.24", + "resolved": "https://registry.npmjs.org/goeasy/-/goeasy-2.13.24.tgz", + "integrity": "sha512-DIoAS4C3E6ViQxW6dUDanQOkKVRwZyhFMLZkwuIqgoamIE36E9fHu0RDumNq4ec+aRt9FnbpA7/ibl38OSTTvA==" }, "node_modules/gopd": { "version": "1.2.0", @@ -2739,9 +2739,9 @@ } }, "goeasy": { - "version": "2.14.9", - "resolved": "https://registry.npmjs.org/goeasy/-/goeasy-2.14.9.tgz", - "integrity": "sha512-AlHx7PCWfIUWKAvjILJx6AzM1GmquGM1MxxA7gPH6TiR9eTtiieJNk9nIXvVYRm0iTC8AfEnekRY2Clghm9xrA==" + "version": "2.13.24", + "resolved": "https://registry.npmjs.org/goeasy/-/goeasy-2.13.24.tgz", + "integrity": "sha512-DIoAS4C3E6ViQxW6dUDanQOkKVRwZyhFMLZkwuIqgoamIE36E9fHu0RDumNq4ec+aRt9FnbpA7/ibl38OSTTvA==" }, "gopd": { "version": "1.2.0", diff --git a/package.json b/package.json index 9baa1bf..485687a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "axios": "^1.13.5", "echarts": "^6.0.0", "element-plus": "^2.13.2", - "goeasy": "^2.14.9", + "goeasy": "2.13.24", "lodash": "^4.18.1", "mitt": "^3.0.1", "sortablejs": "^1.15.7", diff --git a/src/components/ChatDialog.vue b/src/components/ChatDialog.vue index 9c618df..c12f25b 100644 --- a/src/components/ChatDialog.vue +++ b/src/components/ChatDialog.vue @@ -5,7 +5,7 @@ width="700px" :close-on-click-modal="false" destroy-on-close - @open="onDialogOpen" + @opened="onDialogOpened" @close="onDialogClose" >
@@ -54,7 +54,7 @@
@@ -63,16 +63,24 @@ @@ -311,4 +344,4 @@ onUnmounted(() => { .send-sending { text-align: right; margin-top: 2px; margin-right: 42px; } .input-area { padding: 8px 0; } .loading-tip { text-align: center; color: #aaa; font-size: 13px; padding: 10px; } - \ No newline at end of file + diff --git a/src/components/ClubScopeHint.vue b/src/components/ClubScopeHint.vue new file mode 100644 index 0000000..d9990f9 --- /dev/null +++ b/src/components/ClubScopeHint.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/components/KefuConnect.vue b/src/components/KefuConnect.vue index c1b10e1..0a2452b 100644 --- a/src/components/KefuConnect.vue +++ b/src/components/KefuConnect.vue @@ -3,91 +3,108 @@ \ No newline at end of file + diff --git a/src/components/UserScopeHint.vue b/src/components/UserScopeHint.vue new file mode 100644 index 0000000..fa6daf5 --- /dev/null +++ b/src/components/UserScopeHint.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/components/Water_tixianshezhiyemian.vue b/src/components/Water_tixianshezhiyemian.vue index c235a5e..e74c926 100644 --- a/src/components/Water_tixianshezhiyemian.vue +++ b/src/components/Water_tixianshezhiyemian.vue @@ -72,6 +72,7 @@ import { ref, reactive, onMounted } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import request from '@/utils/request' +import { JITUAN_API } from '@/utils/club-context' import WaterTankFishbowl from '@/components/WaterTankFishbowl.vue' const router = useRouter() @@ -119,7 +120,7 @@ const waterRemainingPercent = (role) => { const fetchSettings = async () => { loading.value = true try { - const res = await request.post('/houtai/hthqtxpz', { username: username.value }) + const res = await request.post(JITUAN_API.withdrawGet, { username: username.value }) if (res.code === 0) { const data = res.data roles.value.forEach(role => { @@ -192,7 +193,7 @@ const saveSettings = async () => { saving.value = true try { - const res = await request.post('/houtai/htxgtxsz', { + const res = await request.post(JITUAN_API.withdrawUpdate, { username: username.value, rates: editData.rates, quotas: editData.quotas, diff --git a/src/router/index.js b/src/router/index.js index 7ec26ee..1892ab2 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,5 +1,13 @@ import { createRouter, createWebHistory } from 'vue-router' import Layout from '@/views/Layout.vue' +import request from '@/utils/request' +import { + ensureMenuAccess, + getMenuAccess, + getFirstVisiblePath, + isMenuSessionFresh, + JITUAN_API, +} from '@/utils/club-context' const routes = [ { @@ -9,7 +17,7 @@ const routes = [ { path: '/', component: Layout, - redirect: '/order/platform', + redirect: () => getFirstVisiblePath() || '/order/platform', children: [ @@ -126,6 +134,11 @@ const routes = [ component: () => import('@/views/user/ZuzhangDetail.vue'), meta: { title: '组长详情' } }, + { + path: 'user/identity-tag', + component: () => import('@/views/user/IdentityTag.vue'), + meta: { title: '身份装饰标签' } + }, // 提现管理(原有) { path: 'withdraw/audit', @@ -148,6 +161,16 @@ const routes = [ meta: { title: '提现详情' } }, // 管理员管理(原有) + { + path: 'admin/club-config', + component: () => import('@/views/admin/ClubConfig.vue'), + meta: { title: '俱乐部密钥配置' } + }, + { + path: 'admin/data-scope', + component: () => import('@/views/admin/DataScope.vue'), + meta: { title: '数据范围配置' } + }, { path: 'admin/role', component: () => import('@/views/admin/Role.vue'), @@ -168,6 +191,11 @@ const routes = [ component: () => import('@/views/admin/AdminUserDetail.vue'), meta: { title: '后台用户详情' } }, + { + path: 'admin/operation-log', + component: () => import('@/views/admin/OperationLog.vue'), + meta: { title: '操作日志' } + }, // 处罚管理(原有) { path: 'punishment', @@ -218,6 +246,31 @@ const routes = [ component: () => import('@/views/miniapp/PopupNotice.vue'), meta: { title: '弹窗公告管理' } }, + { + path: 'miniapp/carousel', + component: () => import('@/views/miniapp/CarouselManage.vue'), + meta: { title: '轮播图与公告' } + }, + { + path: 'miniapp/assets', + component: () => import('@/views/miniapp/MiniappAssets.vue'), + meta: { title: '图标与频道' } + }, + { + path: 'miniapp/leixing', + component: () => import('@/views/miniapp/ShangpinLeixingClub.vue'), + meta: { title: '抢单商品类型' } + }, + { + path: 'miniapp/kaohe-tags', + component: () => import('@/views/miniapp/KaoheTagClub.vue'), + meta: { title: '抢单考核标签' } + }, + { + path: 'miniapp/dashou-exam', + component: () => import('@/views/miniapp/DashouExam.vue'), + meta: { title: '打手接单考试' } + }, // ========== 店铺管理(原有) ========== { path: 'shop/list', @@ -250,13 +303,69 @@ const router = createRouter({ routes }) -router.beforeEach((to, from, next) => { +/** 详情页等子路由:父模块在 visible_paths 中即允许访问 */ +function isRouteAllowed(path, visiblePaths) { + if (!visiblePaths?.length) return true + if (visiblePaths.some((p) => p && (path === p || path.startsWith(`${p}/`)))) { + return true + } + const rules = [ + { prefix: '/withdraw/detail/', parents: ['/withdraw/audit', '/withdraw/data'] }, + { prefix: '/punishment/detail/', parents: ['/punishment'] }, + { prefix: '/order/detail/', parents: ['/order/platform', '/order/merchant'] }, + { prefix: '/cross-order/detail/', parents: ['/cross-order'] }, + { prefix: '/user/player/detail/', parents: ['/user/player'] }, + { prefix: '/user/guanli/detail/', parents: ['/user/guanli'] }, + { prefix: '/user/shangjia/detail/', parents: ['/user/shangjia'] }, + { prefix: '/user/zuzhang/detail/', parents: ['/user/zuzhang'] }, + { prefix: '/product/detail/', parents: ['/product/list'] }, + { prefix: '/admin/role/detail/', parents: ['/admin/role'] }, + { prefix: '/admin/user/detail/', parents: ['/admin/user'] }, + { prefix: '/miniapp/assets', parents: ['/miniapp/carousel', '/miniapp/assets'] }, + ] + for (const { prefix, parents } of rules) { + if (path.startsWith(prefix)) { + return visiblePaths.some((p) => parents.includes(p)) + } + } + return false +} + +router.beforeEach(async (to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') - } else { - next() + return } + if (to.path === '/login') { + next() + return + } + + try { + await ensureMenuAccess(async () => { + const res = await request.get(JITUAN_API.menuAccess) + return res.code === 0 ? res.data : null + }) + } catch (e) { + console.error('路由加载菜单权限失败:', e) + } + + const access = getMenuAccess() + if (!isMenuSessionFresh() || !access?.visible_paths?.length) { + next() + return + } + + const path = to.path + if (!isRouteAllowed(path, access.visible_paths)) { + const fallback = getFirstVisiblePath() + if (fallback && path !== fallback) { + next(fallback) + return + } + } + next() }) export default router \ No newline at end of file diff --git a/src/utils/club-context.js b/src/utils/club-context.js new file mode 100644 index 0000000..93592e9 --- /dev/null +++ b/src/utils/club-context.js @@ -0,0 +1,211 @@ +const CLUB_ID_KEY = 'admin_club_id'; +const CLUB_SCOPE_KEY = 'admin_club_scope'; +const CLUB_CONTEXT_KEY = 'admin_club_context'; +const MENU_ACCESS_KEY = 'kefu_menu_access'; +const MENU_SESSION_KEY = 'kefu_menu_session_v'; +const MENU_SESSION_VERSION = '7'; +const DEFAULT_CLUB_ID = 'xq'; + +let menuAccessInflight = null; + +export function getAdminClubId() { + return localStorage.getItem(CLUB_ID_KEY) || DEFAULT_CLUB_ID; +} + +export function getAdminClubScope() { + return localStorage.getItem(CLUB_SCOPE_KEY) || 'single'; +} + +export function setAdminClubContext(ctx) { + if (!ctx) return; + localStorage.setItem(CLUB_CONTEXT_KEY, JSON.stringify(ctx)); + localStorage.setItem(CLUB_ID_KEY, ctx.club_id || DEFAULT_CLUB_ID); + localStorage.setItem(CLUB_SCOPE_KEY, ctx.scope === 'ALL_CLUBS' ? 'all' : 'single'); +} + +export function mergeServerClubContext(serverCtx) { + if (!serverCtx) return; + const prevScope = localStorage.getItem(CLUB_SCOPE_KEY); + const prevClubId = localStorage.getItem(CLUB_ID_KEY); + const merged = { ...serverCtx }; + if (prevScope) { + merged.scope = prevScope === 'all' ? 'ALL_CLUBS' : 'SINGLE_CLUB'; + merged.club_id = prevClubId || merged.club_id || DEFAULT_CLUB_ID; + } + localStorage.setItem(CLUB_CONTEXT_KEY, JSON.stringify(merged)); + if (prevClubId) { + localStorage.setItem(CLUB_ID_KEY, prevClubId); + } else { + localStorage.setItem(CLUB_ID_KEY, merged.club_id || DEFAULT_CLUB_ID); + } + if (prevScope) { + localStorage.setItem(CLUB_SCOPE_KEY, prevScope); + } else { + localStorage.setItem(CLUB_SCOPE_KEY, merged.scope === 'ALL_CLUBS' ? 'all' : 'single'); + } + return merged; +} + +export function getAdminClubContext() { + try { + const raw = localStorage.getItem(CLUB_CONTEXT_KEY); + return raw ? JSON.parse(raw) : null; + } catch { + return null; + } +} + +export function setMenuAccess(access) { + if (!access) return; + localStorage.setItem(MENU_ACCESS_KEY, JSON.stringify(access)); + sessionStorage.setItem(MENU_SESSION_KEY, MENU_SESSION_VERSION); +} + +export function clearMenuAccess() { + localStorage.removeItem(MENU_ACCESS_KEY); + sessionStorage.removeItem(MENU_SESSION_KEY); +} + +export function getMenuAccess() { + try { + const raw = localStorage.getItem(MENU_ACCESS_KEY); + return raw ? JSON.parse(raw) : null; + } catch { + return null; + } +} + +export function isMenuSessionFresh() { + return sessionStorage.getItem(MENU_SESSION_KEY) === MENU_SESSION_VERSION; +} + +export function shouldShowAllMenus(access) { + if (!isMenuSessionFresh()) return true; + if (!access) return true; + if (access.menu_ready === false) return true; + if (!Array.isArray(access.visible_page_ids) || !Array.isArray(access.visible_paths)) return true; + return false; +} + +export function canShowMenuPage(pageId) { + const access = getMenuAccess(); + if (shouldShowAllMenus(access)) return true; + if (access.visible_page_ids.length === 0) return false; + return access.visible_page_ids.includes(pageId); +} + +export function getFirstVisiblePath() { + const access = getMenuAccess(); + if (!access || !Array.isArray(access.visible_paths) || access.visible_paths.length === 0) { + return null; + } + return access.visible_paths[0]; +} + +export function canSwitchClubByMenu() { + const access = getMenuAccess(); + if (!access || !isMenuSessionFresh()) { + const ctx = getAdminClubContext(); + return !!ctx?.can_switch_club; + } + return !!access.can_switch_club; +} + +export async function ensureMenuAccess(fetcher) { + if (!localStorage.getItem('token')) return getMenuAccess(); + if (isMenuSessionFresh() && getMenuAccess()) { + return getMenuAccess(); + } + if (!menuAccessInflight) { + menuAccessInflight = (async () => { + try { + const data = await fetcher(); + if (data) setMenuAccess(data); + return data; + } catch (e) { + console.error('ensureMenuAccess failed:', e); + return getMenuAccess(); + } finally { + menuAccessInflight = null; + } + })(); + } + return menuAccessInflight; +} + +export function applyClubHeaders(config = {}) { + const headers = { ...(config.headers || {}) }; + const clubId = getAdminClubId(); + const scope = getAdminClubScope(); + headers['X-Club-Id'] = clubId; + if (scope === 'all') { + headers['X-Club-Scope'] = 'all'; + } + return { ...config, headers }; +} + +export const JITUAN_API = { + kefuLogin: '/jituan/auth/kefu-login', + meContext: '/jituan/auth/me-context', + menuAccess: '/jituan/auth/menu-access', + clubList: '/jituan/club/list', + caiwu: '/jituan/houtai/caiwu', + szxx: '/jituan/houtai/szxx', + auditLog: '/jituan/houtai/audit-log', + orderTypes: '/jituan/houtai/cwddhqlx', + orderFinance: '/jituan/houtai/cwhqjtddsj', + chongzhiFinance: '/jituan/houtai/cwqtczhq', + huiyuanBankuai: '/jituan/houtai/cwhybkhq', + huiyuanStats: '/jituan/houtai/hybkjtsj', + userSummary: '/jituan/houtai/user-summary', + dashouList: '/jituan/houtai/kefuhqdslb', + guanshiList: '/jituan/houtai/hthqgslb', + shangjiaList: '/jituan/houtai/hqsjgllb', + zuzhangList: '/jituan/houtai/hthqzzlb', + operationLog: '/jituan/houtai/hqczrz', + withdrawGet: '/jituan/houtai/hthqtxpz', + withdrawUpdate: '/jituan/houtai/htxgtxsz', + lunboList: '/jituan/houtai/lunbo-list', + lunboManage: '/jituan/houtai/lunbo-manage', + gonggao: '/jituan/houtai/gonggao', + tupianList: '/jituan/houtai/tupian-list', + tupianManage: '/jituan/houtai/tupian-manage', + popupList: '/jituan/houtai/hthqtcxx', + popupModify: '/jituan/houtai/htxgtcxx', + penaltyStats: '/jituan/houtai/htglyhqcfsltj', + penaltyList: '/jituan/houtai/hthqfklb', + penaltyAction: '/jituan/houtai/glyclfk', + penaltyCreate: '/jituan/houtai/htfksc', + kefuCfgl: '/jituan/houtai/kefu-cfgl', + adminAssignments: '/jituan/houtai/admin-assignments', + clubManage: '/jituan/houtai/club-manage', + memberList: '/jituan/houtai/hthqhylb', + memberUpdate: '/jituan/houtai/htxghyxx', + memberAdd: '/jituan/houtai/httjhy', + memberCatalog: '/jituan/houtai/hthycatalog', + memberEnable: '/jituan/houtai/hthysj', + memberDisable: '/jituan/houtai/hthyxj', + identityTagList: '/jituan/houtai/identity-tag/list', + identityTagManage: '/jituan/houtai/identity-tag/manage', + identityTagBindList: '/jituan/houtai/identity-tag/bind-list', + identityTagBind: '/jituan/houtai/identity-tag/bind', + miniappIconList: '/jituan/houtai/miniapp-icon-list', + miniappIconManage: '/jituan/houtai/miniapp-icon-manage', + pindaoManage: '/jituan/houtai/pindao-manage', + clubLeixingList: '/jituan/houtai/club-leixing-list', + clubLeixingCatalog: '/jituan/houtai/club-leixing-catalog', + clubLeixingEnable: '/jituan/houtai/club-leixing-enable', + clubLeixingDisable: '/jituan/houtai/club-leixing-disable', + clubLeixingIcon: '/jituan/houtai/club-leixing-icon', + clubKaoheList: '/jituan/houtai/club-kaohe-list', + clubKaoheCatalog: '/jituan/houtai/club-kaohe-catalog', + clubKaoheEnable: '/jituan/houtai/club-kaohe-enable', + clubKaoheDisable: '/jituan/houtai/club-kaohe-disable', + khggl: '/jituan/houtai/khggl', + khgglAdd: '/jituan/houtai/khggl-add', + shgxgsj: '/jituan/houtai/shgxgsj', + dashouExamConfig: '/jituan/houtai/dashou-exam-config', + dashouExamQuestionList: '/jituan/houtai/dashou-exam-question-list', + dashouExamQuestionManage: '/jituan/houtai/dashou-exam-question-manage', + dashouExamImageManage: '/jituan/houtai/dashou-exam-image-manage', +}; diff --git a/src/utils/kefuChat.js b/src/utils/kefuChat.js new file mode 100644 index 0000000..cfb0b2b --- /dev/null +++ b/src/utils/kefuChat.js @@ -0,0 +1,406 @@ +import GoEasy from 'goeasy' + +export const KEFU_TEAM_ID = 'support_team' + +const NOTIFY_DEBOUNCE_MS = 2500 +const ONLINE_TIMEOUT_MS = 12000 + +let lastNotifyAt = 0 +let onlineInFlight = null + +export function getCsteam() { + if (!window.goeasy?.im) return null + return window.goeasy.im.csteam(KEFU_TEAM_ID) +} + +export function isGoeasyConnected() { + if (!window.goeasy) return false + try { + const status = window.goeasy.getConnectionStatus() + return status === 'connected' || status === 'reconnected' + } catch (e) { + return !!window.goeasy?.im + } +} + +export function calcUnreadFromConversations(conversations = []) { + return conversations.reduce((sum, c) => sum + (c.unread || 0), 0) +} + +/** 等待 GoEasy 内部 queryTeams 完成,初始化 teamIds,避免 online 成功回调 crash */ +function waitCsTeamsReady(csteam) { + return new Promise((resolve) => { + if (!csteam?.isOnline) { + resolve() + return + } + csteam.isOnline({ + onSuccess: () => resolve(), + onFailed: () => resolve() + }) + }) +} + +export function checkCsOnlineStatus() { + return new Promise((resolve) => { + const csteam = getCsteam() + if (!csteam) { + resolve(false) + return + } + csteam.isOnline({ + onSuccess: (online) => { + window.__kefuCsOnline = !!online + resolve(!!online) + }, + onFailed: () => resolve(false) + }) + }) +} + +export function refreshKefuUnread(emitter) { + if (!window.goeasy?.im) return + const im = window.goeasy.im + let activeUnread = 0 + + im.latestConversations({ + onSuccess: (result) => { + const content = result.content || {} + const convs = content.conversations || [] + const csActive = convs.filter(c => c.type === 'cs' && !c.ended) + activeUnread = content.unreadTotal || calcUnreadFromConversations(csActive) + + im.pendingConversations({ + onSuccess: (pendingResult) => { + const pending = pendingResult.content?.conversations || [] + const pendingUnread = calcUnreadFromConversations(pending) + emitter?.emit('agent-unread', activeUnread + pendingUnread) + }, + onFailed: () => { + emitter?.emit('agent-unread', activeUnread) + } + }) + }, + onFailed: () => {} + }) +} + +function emitCsOnline(emitter, online) { + window.__kefuCsOnline = !!online + emitter?.emit('kefu-cs-online', window.__kefuCsOnline) +} + +export function ensureCsOnline({ phone, onSuccess, onFailed, emitter, force = false } = {}) { + if (!window.goeasy?.im) { + onFailed?.({ content: '消息服务未连接' }) + return + } + const csteam = getCsteam() + if (!csteam) { + onFailed?.({ content: '客服团队未初始化' }) + return + } + + const finishSuccess = () => { + emitCsOnline(emitter, true) + onSuccess?.() + } + const finishFailed = (error) => { + emitCsOnline(emitter, false) + onFailed?.(error) + } + + if (!force && window.__kefuCsOnline) { + checkCsOnlineStatus().then((online) => { + if (online) { + finishSuccess() + } else { + window.__kefuCsOnline = false + doCsOnline(csteam, phone, emitter, finishSuccess, finishFailed) + } + }) + return + } + + if (force) { + window.__kefuCsOnline = false + onlineInFlight = null + } + + doCsOnline(csteam, phone, emitter, finishSuccess, finishFailed) +} + +function doCsOnline(csteam, phone, emitter, onSuccess, onFailed) { + if (onlineInFlight) { + onlineInFlight + .then(() => onSuccess?.()) + .catch((e) => onFailed?.(e)) + return + } + + const displayName = phone || localStorage.getItem('username') || '客服' + let finished = false + + onlineInFlight = waitCsTeamsReady(csteam).then(() => new Promise((resolve, reject) => { + const timer = setTimeout(() => { + if (finished) return + finished = true + onlineInFlight = null + emitCsOnline(emitter, false) + reject({ content: '上线超时,请检查网络后重试' }) + }, ONLINE_TIMEOUT_MS) + + csteam.online({ + teamData: { name: '星阙客服中心', avatar: '' }, + agentData: { name: displayName, avatar: '' }, + onSuccess: () => { + if (finished) return + finished = true + clearTimeout(timer) + onlineInFlight = null + emitCsOnline(emitter, true) + resolve(true) + }, + onFailed: (error) => { + if (finished) return + finished = true + clearTimeout(timer) + onlineInFlight = null + const msg = (error?.content || '').toLowerCase() + if (msg.includes('online') || msg.includes('已') || msg.includes('already')) { + emitCsOnline(emitter, true) + resolve(true) + return + } + console.error('[kefuChat] 上线失败:', error) + emitCsOnline(emitter, false) + reject(error) + } + }) + })) + + onlineInFlight + .then(() => onSuccess?.()) + .catch((e) => onFailed?.(e)) +} + +export function goOnlineManually({ phone, emitter, onSuccess, onFailed } = {}) { + window.__kefuUserWantsOffline = false + ensureCsOnline({ + phone, + emitter, + force: true, + onSuccess, + onFailed + }) +} + +export function goOfflineManually({ emitter, onSuccess, onFailed } = {}) { + window.__kefuUserWantsOffline = true + onlineInFlight = null + const csteam = getCsteam() + if (!csteam) { + emitCsOnline(emitter, false) + onSuccess?.() + return + } + csteam.offline({ + onSuccess: () => { + emitCsOnline(emitter, false) + onSuccess?.() + }, + onFailed: (error) => onFailed?.(error) + }) +} + +export function disconnectKefuChat() { + onlineInFlight = null + if (window.__kefuUnreadCleanup) { + window.__kefuUnreadCleanup() + window.__kefuUnreadCleanup = null + } + if (window.goeasy) { + try { + window.goeasy.disconnect({}) + } catch (e) { + // ignore + } + window.goeasy = null + } + window.__kefuCsOnline = false + window.__kefuUserWantsOffline = false + window.__kefuAgentId = null + window.__kefuAppkey = null +} + +export function getCsMessageCustomerId(message, teamId = KEFU_TEAM_ID, agentId) { + if (!message) return null + const tid = message.teamId || teamId + const aid = agentId || window.__kefuAgentId + const sender = message.senderId + const to = message.to || message.receiverId + + try { + if (typeof message.customerId === 'function') { + const cid = message.customerId() + if (cid && cid !== tid) return cid + } + } catch (e) { + // ignore + } + + const parties = [sender, to].filter(Boolean) + for (const p of parties) { + if (p !== tid && p !== aid && !String(p).startsWith('KF')) return p + } + if (sender === aid && to) return to + if (to === aid && sender) return sender + if (sender === tid && to) return to + if (to === tid && sender) return sender + return null +} + +export function isCsMessageForCustomer(message, customerId, teamId = KEFU_TEAM_ID, agentId) { + if (!message || !customerId) return false + if (message.teamId && message.teamId !== teamId) return false + const cid = getCsMessageCustomerId(message, teamId, agentId) + return cid === customerId +} + +export function buildCsTextMessage(teamId, customerId, customerData, text) { + const message = window.goeasy.im.createTextMessage({ + text, + to: { + type: GoEasy.IM_SCENE.CS, + id: customerId, + data: customerData || { name: customerId, avatar: '' } + } + }) + message.teamId = teamId + message.accepted = true + return message +} + +export function ensureCustomerAccepted(customerId, customerData) { + return new Promise((resolve, reject) => { + if (!window.goeasy?.im || !customerId) { + reject({ content: '连接未就绪' }) + return + } + if (!window.__kefuCsOnline) { + reject({ content: '请先上线后再聊天' }) + return + } + const csteam = getCsteam() + if (!csteam) { + reject({ content: '客服团队未初始化' }) + return + } + csteam.accept({ + customer: { + id: customerId, + data: customerData || { name: customerId, avatar: '' } + }, + onSuccess: () => resolve(), + onFailed: (error) => { + const msg = (error?.content || '').toLowerCase() + if (msg.includes('already') || msg.includes('exist') || msg.includes('已')) { + resolve() + return + } + reject(error) + } + }) + }) +} + +function maybeNotifyNewMessage(emitter, message) { + const aid = window.__kefuAgentId + if (message?.senderId && message.senderId === aid) return + + const now = Date.now() + if (now - lastNotifyAt < NOTIFY_DEBOUNCE_MS) return + lastNotifyAt = now + + const customerId = getCsMessageCustomerId(message) + const preview = message?.payload?.text || message?.payload?.name || '[新消息]' + emitter?.emit('kefu-message-notify', { + customerId, + preview: String(preview).slice(0, 80), + message + }) +} + +function setupGlobalListeners(emitter) { + if (!window.goeasy?.im) return () => {} + + const im = window.goeasy.im + const onRefresh = () => refreshKefuUnread(emitter) + const onCsMessage = (message) => { + emitter?.emit('kefu-cs-new-message', message) + maybeNotifyNewMessage(emitter, message) + refreshKefuUnread(emitter) + } + + im.on(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, onRefresh) + im.on(GoEasy.IM_EVENT.PENDING_CONVERSATIONS_UPDATED, onRefresh) + im.on(GoEasy.IM_EVENT.CS_MESSAGE_RECEIVED, onCsMessage) + + refreshKefuUnread(emitter) + + return () => { + im.off(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, onRefresh) + im.off(GoEasy.IM_EVENT.PENDING_CONVERSATIONS_UPDATED, onRefresh) + im.off(GoEasy.IM_EVENT.CS_MESSAGE_RECEIVED, onCsMessage) + } +} + +export function bindGlobalUnreadListeners(emitter) { + return setupGlobalListeners(emitter) +} + +export function afterGoeasyConnected(emitter, phone) { + if (window.__kefuUnreadCleanup) { + window.__kefuUnreadCleanup() + } + window.__kefuUnreadCleanup = bindGlobalUnreadListeners(emitter) + + if (window.__kefuUserWantsOffline) { + emitCsOnline(emitter, false) + refreshKefuUnread(emitter) + return + } + + ensureCsOnline({ + phone, + emitter, + force: false, + onSuccess: () => refreshKefuUnread(emitter), + onFailed: () => refreshKefuUnread(emitter) + }) +} + +export function resumeKefuSession(emitter, phone) { + if (!isGoeasyConnected()) { + emitter?.emit('kefu-goeasy-ready', false) + return + } + emitter?.emit('kefu-goeasy-ready', true) + afterGoeasyConnected(emitter, phone) +} + +export function onGoeasyReconnected(emitter, phone) { + emitter?.emit('kefu-goeasy-ready', true) + if (window.__kefuUserWantsOffline) { + emitCsOnline(emitter, false) + refreshKefuUnread(emitter) + return + } + ensureCsOnline({ + phone, + emitter, + force: false, + onSuccess: () => refreshKefuUnread(emitter), + onFailed: () => refreshKefuUnread(emitter) + }) +} diff --git a/src/utils/request.js b/src/utils/request.js index 0bf71ba..0c5c2ad 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -2,6 +2,7 @@ import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' +import { applyClubHeaders } from '@/utils/club-context' // 创建 axios 实例,不设置 baseURL(改为动态拼接) const service = axios.create({ @@ -22,7 +23,7 @@ service.interceptors.request.use( config.url = (window.$baseURL || '') + config.url } - return config + return applyClubHeaders(config) }, (error) => Promise.reject(error) ) diff --git a/src/views/Layout.vue b/src/views/Layout.vue index da295b1..469b44e 100644 --- a/src/views/Layout.vue +++ b/src/views/Layout.vue @@ -19,27 +19,27 @@ - + - 板块配置 - 财务数据 + 板块配置 + 财务数据 - + - 考核官管理 - 考核配置 - 考核记录 + 考核官管理 + 考核配置 + 考核记录 @@ -47,109 +47,119 @@ - + - 平台订单 - 商家订单 + 平台订单 + 商家订单 - + - 跨平台订单 - 跨平台设置 - 跨平台数据 - 预结算管理 + 跨平台订单 + 跨平台设置 + 跨平台数据 + 预结算管理 - + - 打手管理 - 管事管理 - 商家管理 - 组长管理 + 打手管理 + 管事管理 + 商家管理 + 组长管理 + 身份装饰标签 - + - 提现审核 - 提现数据 - 提现设置 + 提现审核 + 提现数据 + 提现设置 - + - 角色管理 - 后台用户 + 角色管理 + 数据范围配置 + 俱乐部密钥配置 + 后台用户 + 操作日志 - + 处罚管理 - + - 商品列表 - 商品类型专区管理 - 商品数据分析 + 商品列表 + 商品类型专区管理 + 商品数据分析 - + - 会员管理 - 会员数据分析 + 会员管理 + 会员数据分析 - + - 弹窗公告管理 + 轮播图与公告 + 图标与频道 + 弹窗公告管理 + 抢单商品类型 + 抢单考核标签 + 打手接单考试 + 提现与收款设置 - + - 店铺列表 - 店铺提现申请 - 店铺商品管理 + 店铺列表 + 店铺提现申请 + 店铺商品管理 - + 会话管理 -
- {{ currentTitle }} -
-
-
-
{{ stat.icon }}
-
-
{{ stat.label }}
-
{{ stat.value }}
-
+
+
+ {{ currentTitle }} + + + + + {{ scopeBadgeText }} + {{ adminRoleName }} +
+
+ {{ userPhone }} + 退出
-
- {{ userPhone }} - 退出 +
+
+
+
{{ stat.icon }}
+
+
{{ stat.label }}
+
{{ stat.value }}
+
+
+
@@ -196,18 +231,43 @@ + @@ -501,19 +676,67 @@ onUnmounted(() => { background: rgba(6, 12, 20, 0.7); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(0, 242, 255, 0.3); + display: flex; + flex-direction: column; + padding: 10px 24px 12px; + gap: 10px; + height: auto; + min-height: 80px; +} + +.header-row { display: flex; align-items: center; + width: 100%; +} + +.header-row-main { justify-content: space-between; - padding: 0 24px; - height: 80px; - gap: 20px; - flex-wrap: wrap; + gap: 16px; +} + +.header-row-stats { + justify-content: center; } .header-left { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 12px; + flex-wrap: nowrap; +} + +.club-select { + width: 200px; flex-shrink: 0; } +.club-badge { + white-space: nowrap; + font-size: 12px; + padding: 4px 10px; + border-radius: 4px; + color: rgba(0, 242, 255, 0.9); + border: 1px solid rgba(0, 242, 255, 0.35); + background: rgba(0, 242, 255, 0.08); + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +} + +.role-badge { + white-space: nowrap; + flex-shrink: 0; + font-size: 12px; + padding: 4px 10px; + border-radius: 4px; + color: rgba(0, 242, 255, 0.85); + border: 1px solid rgba(0, 242, 255, 0.35); + background: rgba(0, 242, 255, 0.08); +} + .page-title { font-size: 1.4rem; font-weight: 700; @@ -529,9 +752,10 @@ onUnmounted(() => { .stats-mini { display: flex; gap: 16px; - flex-wrap: wrap; - flex: 1; + flex-wrap: nowrap; justify-content: center; + overflow-x: auto; + max-width: 100%; } .stat-mini-card { @@ -640,15 +864,13 @@ onUnmounted(() => { /* 响应式 */ @media screen and (max-width: 1024px) { .header { - height: auto; - padding: 12px 20px; + padding: 10px 16px; + } + .header-row-main { flex-wrap: wrap; } .stats-mini { - order: 3; - width: 100%; justify-content: flex-start; - margin-top: 8px; } .stat-mini-card { padding: 6px 16px; @@ -681,4 +903,22 @@ onUnmounted(() => { .agent-badge { margin-left: 4px; } + + + \ No newline at end of file diff --git a/src/views/Login.vue b/src/views/Login.vue index eed677d..c4ed746 100644 --- a/src/views/Login.vue +++ b/src/views/Login.vue @@ -65,6 +65,7 @@ import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { User, Lock, Key } from '@element-plus/icons-vue' import axios from 'axios' // 直接使用 axios,不经过封装的 request +import { JITUAN_API, setAdminClubContext, setMenuAccess } from '@/utils/club-context' const router = useRouter() const loading = ref(false) @@ -92,9 +93,9 @@ const handleLogin = async () => { try { // 直接使用 axios 发送请求,baseURL 从 window.$baseURL 获取 const response = await axios.post( - window.$baseURL + '/yonghu/kefujinru', + window.$baseURL + JITUAN_API.kefuLogin, { - phone: form.username, // 字段与后端一致 + phone: form.username, password: form.password, erjimima: form.ip }, @@ -109,12 +110,22 @@ const handleLogin = async () => { const res = response.data if (res.code === 0) { - // 登录成功 const token = res.data.token localStorage.setItem('token', token) localStorage.setItem('username', form.username) + if (res.data.club_context) { + const ctx = { ...res.data.club_context } + if (res.data.menu_access) { + setMenuAccess(res.data.menu_access) + ctx.can_switch_club = res.data.menu_access.can_switch_club + } + setAdminClubContext(ctx) + } else if (res.data.menu_access) { + setMenuAccess(res.data.menu_access) + } ElMessage.success('登录成功') - router.push('/') + const firstPath = res.data.menu_access?.visible_paths?.[0] + router.push(firstPath || '/order/platform') } else if (res.code === 4) { // 账号被封禁 ElMessage.error('账号已被封禁') diff --git a/src/views/Punishment.vue b/src/views/Punishment.vue index fe4e3a9..8f3abb6 100644 --- a/src/views/Punishment.vue +++ b/src/views/Punishment.vue @@ -1,8 +1,8 @@ + + + + + + + + + + + + + + + + + +
@@ -219,10 +270,12 @@ import { ref, reactive, computed, onMounted } from 'vue' import { ElMessage } from 'element-plus' import request from '@/utils/request' import { cloneDeep } from 'lodash-es' +import { JITUAN_API, getAdminClubScope } from '@/utils/club-context' const username = localStorage.getItem('username') || '' const hasPermission = ref(true) const loading = ref(false) +const isAllClubScope = computed(() => getAdminClubScope() === 'all') // 筛选条件 const filter = reactive({ @@ -248,6 +301,13 @@ const detailVisible = ref(false) const isEditMode = ref(false) const currentExaminer = ref({}) const saving = ref(false) +const addVisible = ref(false) +const addSaving = ref(false) +const addForm = reactive({ + yonghuid: '', + bankuai_ids: [], + is_renzheng: true, +}) // 板块标签统计 const bankuaiTagsData = ref([]) @@ -286,7 +346,7 @@ const fetchExaminerList = async () => { page_size: pagination.pageSize, ...filter } - const res = await request.post('/houtai/khggl', params) + const res = await request.post(JITUAN_API.khggl, params) if (res.code === 0) { const list = (res.data.list || []).map(item => ({ ...item, @@ -354,7 +414,7 @@ const addBankuai = async () => { return } try { - const res = await request.post('/houtai/shgxgsj', { + const res = await request.post(JITUAN_API.shgxgsj, { phone: username, yonghuid: currentExaminer.value.yonghuid, action: 'add_bankuai', @@ -382,7 +442,7 @@ const addBankuai = async () => { // 移除板块 const removeBankuai = async (bankuaiId) => { try { - const res = await request.post('/houtai/shgxgsj', { + const res = await request.post(JITUAN_API.shgxgsj, { phone: username, yonghuid: currentExaminer.value.yonghuid, action: 'remove_bankuai', @@ -413,7 +473,7 @@ const saveExaminer = async () => { saving.value = true try { - const res = await request.post('/houtai/shgxgsj', payload) + const res = await request.post(JITUAN_API.shgxgsj, payload) if (res.code === 0) { ElMessage.success('修改成功') isEditMode.value = false @@ -434,6 +494,53 @@ const saveExaminer = async () => { } } +const openAddDialog = () => { + if (isAllClubScope.value) { + ElMessage.warning('请切换到具体小程序视图后再添加考核官') + return + } + addForm.yonghuid = '' + addForm.bankuai_ids = [] + addForm.is_renzheng = true + addVisible.value = true +} + +const closeAddDialog = () => { + addVisible.value = false +} + +const submitAddExaminer = async () => { + const uid = (addForm.yonghuid || '').trim() + if (!uid) { + ElMessage.warning('请输入用户ID') + return + } + if (!addForm.bankuai_ids.length) { + ElMessage.warning('请选择至少一个板块') + return + } + addSaving.value = true + try { + const res = await request.post(JITUAN_API.khgglAdd, { + phone: username, + yonghuid: uid, + bankuai_ids: addForm.bankuai_ids, + is_renzheng: addForm.is_renzheng, + }) + if (res.code === 0) { + ElMessage.success(res.msg || '添加成功') + addVisible.value = false + fetchExaminerList() + } else { + ElMessage.error(res.msg || '添加失败') + } + } catch (e) { + ElMessage.error('网络错误') + } finally { + addSaving.value = false + } +} + onMounted(() => { if (!username) { ElMessage.error('未登录') @@ -474,6 +581,7 @@ onMounted(() => { } .filter-input { width: 160px; } .filter-select { width: 140px; } +.scope-alert { margin-bottom: 16px; } /* 板块标签统计面板 */ .stat-collapse { diff --git a/src/views/chat/Agent.vue b/src/views/chat/Agent.vue index b2da6c3..3eeca55 100644 --- a/src/views/chat/Agent.vue +++ b/src/views/chat/Agent.vue @@ -1,15 +1,11 @@
@@ -335,6 +337,14 @@ {{ playerInfo.yaoqingren || '--' }} + 更换邀请人 @@ -348,6 +358,23 @@
+ + + + + {{ playerInfo.yaoqingren || '无' }} + + + +
新邀请人必须是已存在的管事用户
+
+
+ +
+ @@ -404,8 +431,63 @@ const memberAdding = ref(false) const userPermissions = ref([]) const isGoldTitle = computed(() => playerInfo.value.chenghao === '金牌选手') -const hasMemberPermission = computed(() => userPermissions.value.includes('001ff') || userPermissions.value.includes('001gg')) +const hasMemberPermission = computed(() => + userPermissions.value.includes('000001') || + userPermissions.value.includes('001ff') || + userPermissions.value.includes('001gg') +) const hasChenghaoPermission = computed(() => userPermissions.value.includes('001hh')) +const canChangeInviter = computed(() => userPermissions.value.includes('001ee') || userPermissions.value.includes('000001')) + +const inviterDialogVisible = ref(false) +const newInviterId = ref('') +const inviterChanging = ref(false) + +const openInviterDialog = () => { + newInviterId.value = '' + inviterDialogVisible.value = true +} + +const submitChangeInviter = async () => { + const newId = newInviterId.value.trim() + if (!newId) { + ElMessage.warning('请输入新邀请人ID') + return + } + if (newId === playerId.value) { + ElMessage.warning('不能将自己设为邀请人') + return + } + try { + await ElMessageBox.confirm( + `确认将打手 ${playerId.value} 的邀请人更换为 ${newId}?新上级必须是管事。`, + '确认更换', + { type: 'warning' } + ) + } catch { return } + + inviterChanging.value = true + try { + const res = await request.post('/houtai/htghyqr', { + username: username.value, + yonghuid: playerId.value, + target_type: 'dashou', + new_yaoqingren: newId + }) + if (res.code === 0) { + ElMessage.success('邀请人更换成功') + inviterDialogVisible.value = false + await fetchDetail() + } else { + ElMessage.error(res.msg || '更换失败') + } + } catch (e) { + console.error(e) + ElMessage.error('更换失败') + } finally { + inviterChanging.value = false + } +} // 辅助函数 const getFullImageUrl = (url) => { @@ -448,7 +530,10 @@ const fetchDetail = async () => { }) if (res.code === 0) { const data = res.data - playerInfo.value = data.user_info || {} + playerInfo.value = { + ...(data.user_info || {}), + create_time: data.user_info?.create_time || data.user_info?.CreateTime || '', + } originalInfo.value = JSON.parse(JSON.stringify(playerInfo.value)) memberList.value = data.member_list || [] allMembers.value = data.all_members || [] @@ -569,6 +654,13 @@ const handleChenghaoChange = async () => { // 添加会员 const openMemberDialog = () => { + if (!hasMemberPermission.value) { + ElMessage.warning('无添加会员权限(需要 001ff 或超级管理)') + return + } + if (allMembers.value.length === 0) { + ElMessage.warning('会员类型列表为空,请刷新页面后重试') + } memberForm.huiyuan_id = '' memberForm.days = 30 memberDialogVisible.value = true @@ -655,6 +747,14 @@ onMounted(() => { text-align: center; padding: 20px; } +.member-hint { + font-size: 12px; + color: #909399; + margin-bottom: 8px; +} +.member-hint.warn { + color: #e6a23c; +} /* 其余样式保持不变(从原代码复制) */ @@ -810,4 +910,9 @@ onMounted(() => { font-size: 20px; } } +.form-tip { + font-size: 12px; + color: #909399; + margin-top: 4px; +} \ No newline at end of file diff --git a/src/views/user/Shangjia.vue b/src/views/user/Shangjia.vue index 3a977c9..2b9b114 100644 --- a/src/views/user/Shangjia.vue +++ b/src/views/user/Shangjia.vue @@ -8,6 +8,7 @@
+
@@ -159,6 +160,8 @@ import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { Search, Refresh } from '@element-plus/icons-vue' import request from '@/utils/request' +import { JITUAN_API } from '@/utils/club-context' +import UserScopeHint from '@/components/UserScopeHint.vue' const router = useRouter() @@ -221,7 +224,7 @@ const fetchMerchants = async () => { params.status = statusFilter.value } - const res = await request.post('/houtai/hqsjgllb', params) + const res = await request.post(JITUAN_API.shangjiaList, params) if (res.code === 0) { merchants.value = res.data.list || [] totalCount.value = res.data.total || 0 diff --git a/src/views/user/ShangjiaDetail.vue b/src/views/user/ShangjiaDetail.vue index 5aeaf51..99db929 100644 --- a/src/views/user/ShangjiaDetail.vue +++ b/src/views/user/ShangjiaDetail.vue @@ -25,6 +25,15 @@ {{ merchant.zhuangtai === 1 ? '正常' : '已封禁' }} + 优质商家 +
+
+ 金牌商家 +
电话:{{ merchant.dianhua }}
@@ -153,6 +162,7 @@ const merchantId = route.params.id const merchant = ref(null) const loading = ref(false) const submitting = ref(false) +const youzhiSaving = ref(false) // 余额弹窗 const balanceDialogVisible = ref(false) @@ -213,7 +223,10 @@ const fetchMerchantDetail = async () => { } const res = await request.post('/houtai/hthqsjxq', { username, yonghuid: merchantId }) if (res.code === 0) { - merchant.value = res.data + merchant.value = { + ...res.data, + create_time: res.data?.create_time || res.data?.CreateTime || '', + } } else if (res.code === 403) { ElMessage.error(res.msg) goBack() @@ -278,6 +291,31 @@ const submitBalanceChange = async () => { } // 封禁 +const handleYouzhiChange = async (val) => { + youzhiSaving.value = true + try { + const username = localStorage.getItem('username') + const res = await request.post('/houtai/xgsjxx', { + username, + yonghuid: merchantId, + action: 'set_youzhi', + is_youzhi_shangjia: val, + }) + if (res.code === 0) { + ElMessage.success(res.msg || '设置成功') + merchant.value.is_youzhi_shangjia = val + } else { + merchant.value.is_youzhi_shangjia = !val + ElMessage.error(res.msg || '设置失败') + } + } catch (error) { + merchant.value.is_youzhi_shangjia = !val + ElMessage.error('网络错误') + } finally { + youzhiSaving.value = false + } +} + const handleBan = async () => { try { await ElMessageBox.confirm('确定封禁该商家?封禁后无法派单和登录。', '封禁确认', { @@ -384,6 +422,15 @@ onMounted(() => { } .uid { font-size: 18px; font-weight: 600; color: #1a2634; } .nickname { font-size: 14px; color: #6b7a88; } +.youzhi-tag { margin-left: 8px; } +.youzhi-row { + display: flex; + align-items: center; + gap: 12px; + margin: 8px 0 12px; + font-size: 14px; + color: #4a5a6e; +} .contact-info { display: flex; gap: 24px; diff --git a/src/views/user/Zuzhang.vue b/src/views/user/Zuzhang.vue index e17d648..3cfcafe 100644 --- a/src/views/user/Zuzhang.vue +++ b/src/views/user/Zuzhang.vue @@ -8,6 +8,7 @@
+
@@ -172,6 +173,8 @@ import { useRouter, useRoute } from 'vue-router' import { ElMessage } from 'element-plus' import { Search, Refresh } from '@element-plus/icons-vue' import request from '@/utils/request' +import { JITUAN_API } from '@/utils/club-context' +import UserScopeHint from '@/components/UserScopeHint.vue' const router = useRouter() const route = useRoute() @@ -300,7 +303,7 @@ const fetchLeaders = async () => { params.commission_value = commissionValue.value } - const res = await request.post('/houtai/hthqzzlb', params) + const res = await request.post(JITUAN_API.zuzhangList, params) if (res.code === 0) { leaders.value = res.data.list || [] diff --git a/src/views/user/ZuzhangDetail.vue b/src/views/user/ZuzhangDetail.vue index cab3415..45ad934 100644 --- a/src/views/user/ZuzhangDetail.vue +++ b/src/views/user/ZuzhangDetail.vue @@ -194,6 +194,10 @@ :disabled="!isEditMode" />
+
+ 第1次:会员默认分成或下方「首次定制」。第2次及以后:仅当配置了对应次数分红才发放; + 若开启「永久分红」,则第2次起每次按永久金额发放(无需逐次添加)。未配置且未开永久则无分红。 +
@@ -268,6 +272,11 @@
+ 添加第2次分红配置
+
+ + + 添加第{{ (extraTimesList.length ? Math.max(...extraTimesList) : 1) + 1 }}次分红配置 + +
暂无额外次数分红配置
@@ -464,7 +473,10 @@ const fetchZuzhangDetail = async () => { zuzhang.value = data allMembers.value = data.all_members || [] // 填充基础表单 - Object.assign(baseForm, data.base) + Object.assign(baseForm, { + ...data.base, + create_time: data.base?.create_time || data.base?.CreateTime || '', + }) Object.assign(balanceForm, data.balance) permanentDividend.enabled = data.permanent_enabled permanentDividend.amount = data.permanent_amount diff --git a/src/views/withdraw/Audit.vue b/src/views/withdraw/Audit.vue index b906003..0f4fdb1 100644 --- a/src/views/withdraw/Audit.vue +++ b/src/views/withdraw/Audit.vue @@ -1,5 +1,6 @@