From 3f621a75ad3d06f0e97b0dea7155f50bd8fab33d Mon Sep 17 00:00:00 2001 From: ZhuRui Date: Tue, 4 Aug 2026 11:21:30 +0800 Subject: [PATCH 01/25] =?UTF-8?q?fix:=20=E5=AF=B9=E6=8E=A5=E8=B5=9B?= =?UTF-8?q?=E4=BA=8B=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/EventDetailModal.module.less | 26 ++++ .../list/components/EventDetailModal.tsx | 128 ++++++++++-------- .../list/components/EventRegulationModal.tsx | 2 +- src/pages/events/list/index.tsx | 12 +- .../events/list/model/useEventListColumns.ts | 2 +- 5 files changed, 105 insertions(+), 65 deletions(-) diff --git a/src/pages/events/list/components/EventDetailModal.module.less b/src/pages/events/list/components/EventDetailModal.module.less index 7d4652f..c27b15f 100644 --- a/src/pages/events/list/components/EventDetailModal.module.less +++ b/src/pages/events/list/components/EventDetailModal.module.less @@ -65,4 +65,30 @@ font-size: 13px; color: rgba(0, 0, 0, 0.65); } + + .idImageGrid { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + .idImageCell { + width: 128px; + height: 82px; + border-radius: 4px; + overflow: hidden; + border: 1px solid #f0f0f0; + flex-shrink: 0; + + :global(.ant-image) { + width: 100%; + height: 100%; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + } } diff --git a/src/pages/events/list/components/EventDetailModal.tsx b/src/pages/events/list/components/EventDetailModal.tsx index 12c4f43..224f9cb 100644 --- a/src/pages/events/list/components/EventDetailModal.tsx +++ b/src/pages/events/list/components/EventDetailModal.tsx @@ -1,6 +1,5 @@ import { defineComponent, reactive, ref } from 'vue'; -import { Modal, Descriptions, Image, Table, Pagination, Space, Spin } from 'ant-design-vue'; -import { EyeOutlined } from '@ant-design/icons-vue'; +import { Modal, Descriptions, Image, Table, Pagination, Spin } from 'ant-design-vue'; import { message } from 'ant-design-vue'; import { getEventDetail, @@ -30,23 +29,19 @@ const formatGender = (v: string) => GENDER_MAP[v] ?? v; const PAGE_SIZE = 10; -/** 渲染身份证图片 */ +/** 渲染身份证图片(cover 填充,与封面显示方式一致) */ const renderIdImages = (imgList: string[], singleImg: string) => { const urls = imgList.length > 0 ? imgList : singleImg ? [singleImg] : []; if (urls.length === 0) return -; return ( - +
{urls.map((url) => ( - }} - /> +
+ +
))} - +
); }; @@ -62,11 +57,18 @@ const PLAYER_COLUMNS = [ customRender: ({ text }: { text: string }) => formatGender(text), }, { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 }, - { title: '证件号', dataIndex: 'idCard', key: 'idCard', width: 180 }, + { + title: '证件号', + dataIndex: 'idCard', + key: 'idCard', + width: 180, + customRender: ({ record }: { record?: TournamentAdminInfoSignupVO }) => + record?.idCard ? record.idCard : -, + }, { title: '证件图片', key: 'idCardImgs', - width: 180, + width: 280, customRender: ({ record }: { record?: TournamentAdminInfoSignupVO }) => record ? renderIdImages(record.idCardImgList || [], record.idCardImg || '') : -, }, @@ -221,59 +223,69 @@ export default defineComponent({ {/* ===== 封面 ===== */} -
-
封面
- -
+ {detail.value.img && ( +
+
封面
+ +
+ )} {/* ===== 赛事公告 ===== */} -
-
赛事公告
-
{detail.value.notice}
-
+ {detail.value.notice && ( +
+
赛事公告
+
{detail.value.notice}
+
+ )} {/* ===== 组别信息 ===== */} -
-
组别信息
- {detail.value.categoryList.map((cat: InnerCategoryVO) => ( -
-
- {cat.name}({CATEGORY_TYPE_MAP[cat.type] ?? cat.type}) -
- -
- 0 && ( +
+
组别信息
+ {detail.value.categoryList.map((cat: InnerCategoryVO) => ( +
+
+ {cat.name}({CATEGORY_TYPE_MAP[cat.type] ?? cat.type}) +
+
handlePlayerPageChange(cat.id, page)} + pagination={false} + bordered /> - - - ))} - - - {/* ===== 分组信息 ===== */} -
-
分组信息
-
- {detail.value.groupInfoList.map((d: string) => ( -
- {d} + {categoryPlayerTotal.value[cat.id] > 0 && ( +
+ handlePlayerPageChange(cat.id, page)} + /> +
+ )}
))}
-
+ )} + + {/* ===== 分组信息 ===== */} + {detail.value.groupInfoList && detail.value.groupInfoList.length > 0 && ( +
+
分组信息
+
+ {detail.value.groupInfoList.map((d: string) => ( +
+ {d} +
+ ))} +
+
+ )} ) : null} diff --git a/src/pages/events/list/components/EventRegulationModal.tsx b/src/pages/events/list/components/EventRegulationModal.tsx index 6ff5289..ff09772 100644 --- a/src/pages/events/list/components/EventRegulationModal.tsx +++ b/src/pages/events/list/components/EventRegulationModal.tsx @@ -60,7 +60,7 @@ export default defineComponent({ destroyOnClose wrapClassName={styles.eventRegulationModalMain} > - +
{ruleData.value ? ( <> diff --git a/src/pages/events/list/index.tsx b/src/pages/events/list/index.tsx index 22d49b0..f0967c8 100644 --- a/src/pages/events/list/index.tsx +++ b/src/pages/events/list/index.tsx @@ -19,7 +19,7 @@ import { ID_CARD_IMAGE_OPTIONS, } from './model/useEventListModel'; import { useEventListColumns } from './model/useEventListColumns'; -import { regionOptions } from '@/utils/areaData'; +import { getProvinceCityCascaderOptions } from '@/utils/areaData'; import { useContainerSize } from '@/hooks'; import EventRegulationModal from './components/EventRegulationModal'; import EventDetailModal from './components/EventDetailModal'; @@ -64,6 +64,9 @@ export default defineComponent({ const { columns } = useEventListColumns(); const { containerRef, height } = useContainerSize(); + /** 省市两级 Cascader 数据(无区) */ + const provinceCityOptions = getProvinceCityCascaderOptions(); + /** 渲染赛事名称(超长省略 + Tooltip) */ const renderName = (text: string) => { const maxLen = 15; @@ -239,14 +242,13 @@ export default defineComponent({ onUpdate:value={(val: any) => (filterForm.idCardImg = val || '')} /> - + (filterForm.region = val || [])} /> diff --git a/src/pages/events/list/model/useEventListColumns.ts b/src/pages/events/list/model/useEventListColumns.ts index 4c9e6fe..e0e2276 100644 --- a/src/pages/events/list/model/useEventListColumns.ts +++ b/src/pages/events/list/model/useEventListColumns.ts @@ -38,7 +38,7 @@ export function useEventListColumns() { `${record.startTimeBegin} 至 ${record.startTimeEnd}`, }, { - title: '省市区', + title: '省市区域', dataIndex: 'venueArea', key: 'venueArea', width: 200, -- 2.52.0 From 256ba7bd0af13f239cce198f48d6b89252e82d48 Mon Sep 17 00:00:00 2001 From: ZhuRui Date: Tue, 4 Aug 2026 11:44:47 +0800 Subject: [PATCH 02/25] =?UTF-8?q?fix:=20=E8=AF=A6=E6=83=85table=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=8F=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/EventDetailModal.module.less | 5 +++++ .../list/components/EventDetailModal.tsx | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/pages/events/list/components/EventDetailModal.module.less b/src/pages/events/list/components/EventDetailModal.module.less index c27b15f..60753f1 100644 --- a/src/pages/events/list/components/EventDetailModal.module.less +++ b/src/pages/events/list/components/EventDetailModal.module.less @@ -49,6 +49,11 @@ font-weight: 500; } + .tableWrap { + max-height: 200px; + overflow-y: auto; + } + .paginationRow { display: flex; justify-content: flex-end; diff --git a/src/pages/events/list/components/EventDetailModal.tsx b/src/pages/events/list/components/EventDetailModal.tsx index 224f9cb..2b35d24 100644 --- a/src/pages/events/list/components/EventDetailModal.tsx +++ b/src/pages/events/list/components/EventDetailModal.tsx @@ -247,15 +247,16 @@ export default defineComponent({
{cat.name}({CATEGORY_TYPE_MAP[cat.type] ?? cat.type})
-
+
+
+ {categoryPlayerTotal.value[cat.id] > 0 && (
Date: Tue, 4 Aug 2026 13:52:54 +0800 Subject: [PATCH 03/25] =?UTF-8?q?fix:=20=E5=B0=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/OrderDetailModal.module.less | 21 +++++++++++++----- .../orders/components/OrderDetailModal.tsx | 22 ++++++++++--------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/pages/events/orders/components/OrderDetailModal.module.less b/src/pages/events/orders/components/OrderDetailModal.module.less index 0746a27..05743f2 100644 --- a/src/pages/events/orders/components/OrderDetailModal.module.less +++ b/src/pages/events/orders/components/OrderDetailModal.module.less @@ -79,6 +79,17 @@ margin-bottom: 12px; } + .tableWrap { + max-height: 200px; + overflow-y: auto; + } + + .paginationRow { + display: flex; + justify-content: flex-end; + margin-top: 8px; + } + .refundSummary { display: flex; gap: 32px; @@ -181,8 +192,8 @@ } } -.orderModalWrap { - :global(.ant-modal-body) { - padding-top: 16px; - } -} +// .orderModalWrap { +// :global(.ant-modal-body) { +// padding-top: 16px; +// } +// } diff --git a/src/pages/events/orders/components/OrderDetailModal.tsx b/src/pages/events/orders/components/OrderDetailModal.tsx index 9f8e374..d1872da 100644 --- a/src/pages/events/orders/components/OrderDetailModal.tsx +++ b/src/pages/events/orders/components/OrderDetailModal.tsx @@ -255,16 +255,18 @@ export default defineComponent({
选手信息
-
-
+
+
+ +
Date: Tue, 4 Aug 2026 17:01:55 +0800 Subject: [PATCH 04/25] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=20banner?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2=E7=9B=B8=E5=85=B3=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/banner/types.ts | 6 +- src/config/mock/bannerList.ts | 66 +- .../banner/components/BannerFormModal.tsx | 785 +++++++++++------- src/pages/events/banner/model/services.ts | 10 +- .../events/banner/model/useBannerModel.ts | 33 +- src/pages/events/orders/index.tsx | 2 +- src/pages/events/users/index.tsx | 28 +- src/pages/events/users/model/services.ts | 57 +- src/pages/events/users/model/useUserModel.ts | 45 +- src/pages/finance/wallet/model/services.ts | 4 +- src/pages/finance/withdraw/model/services.ts | 6 +- src/utils/areaData.ts | 70 ++ 12 files changed, 699 insertions(+), 413 deletions(-) diff --git a/src/api/banner/types.ts b/src/api/banner/types.ts index b9946de..d95d867 100644 --- a/src/api/banner/types.ts +++ b/src/api/banner/types.ts @@ -8,12 +8,12 @@ export interface TournamentAdminBannerListVO { img: string; title: string; type: string; - linkUrl: string; + params: string; area: string; showTimeBegin: string; showTimeEnd: string; status: string; - createTime: string; + createDate: string; } /** Banner 列表查询参数 */ @@ -28,7 +28,7 @@ export interface BannerSaveParams { img: string; title: string; type: string; - linkUrl: string; + params: string; area: string; showTimeBegin: string; showTimeEnd: string; diff --git a/src/config/mock/bannerList.ts b/src/config/mock/bannerList.ts index ea7a532..cca6e42 100644 --- a/src/config/mock/bannerList.ts +++ b/src/config/mock/bannerList.ts @@ -13,12 +13,12 @@ export interface MockBannerVO { img: string; title: string; type: string; - linkUrl: string; + params: string; area: string; showTimeBegin: string; showTimeEnd: string; status: string; - createTime: string; + createDate: string; } export const MOCK_BANNER_LIST: MockBannerVO[] = [ @@ -27,143 +27,143 @@ export const MOCK_BANNER_LIST: MockBannerVO[] = [ img: 'https://picsum.photos/seed/banner1/80/44', title: '2026全国青少年羽毛球锦标赛火热报名中', type: '1', - linkUrl: '', - area: '广东省深圳市', + params: 'EV20260701001', + area: '深圳市', showTimeBegin: '2026-08-01 00:00', showTimeEnd: '2026-08-31 23:59', status: '1', - createTime: '2026-07-20 08:00:00', + createDate: '2026-07-20 08:00:00', }, { id: '2', img: 'https://picsum.photos/seed/banner2/80/44', title: '马拉松城市联赛限时优惠', type: '2', - linkUrl: 'https://example.com/marathon', - area: '浙江省杭州市', + params: 'https://example.com/marathon', + area: '杭州市', showTimeBegin: '2026-08-15 00:00', showTimeEnd: '2026-09-15 23:59', status: '1', - createTime: '2026-07-19 11:30:00', + createDate: '2026-07-19 11:30:00', }, { id: '3', img: 'https://picsum.photos/seed/banner3/80/44', title: '健身运动会专属福利', type: '1', - linkUrl: '', + params: 'EV20260701003', area: '北京市', showTimeBegin: '2026-07-01 00:00', showTimeEnd: '2026-07-30 23:59', status: '1', - createTime: '2026-07-18 14:20:00', + createDate: '2026-07-18 14:20:00', }, { id: '4', img: 'https://picsum.photos/seed/banner4/80/44', title: '平台品牌宣传活动', type: '2', - linkUrl: 'https://example.com/promo', + params: 'https://example.com/promo', area: '上海市', showTimeBegin: '2026-06-01 00:00', showTimeEnd: '2026-06-30 23:59', status: '0', - createTime: '2026-07-17 18:05:00', + createDate: '2026-07-17 18:05:00', }, { id: '5', img: 'https://picsum.photos/seed/banner5/80/44', title: '2026全国青少年羽毛球锦标赛火热报名中', type: '1', - linkUrl: '', - area: '广东省深圳市', + params: 'EV20260701005', + area: '深圳市', showTimeBegin: '2026-09-01 00:00', showTimeEnd: '2026-09-30 23:59', status: '1', - createTime: '2026-07-16 21:15:00', + createDate: '2026-07-16 21:15:00', }, { id: '6', img: 'https://picsum.photos/seed/banner6/80/44', title: '马拉松城市联赛限时优惠', type: '3', - linkUrl: '/pages/activity/detail', - area: '浙江省杭州市', + params: '/pages/activity/detail', + area: '杭州市', showTimeBegin: '2026-10-01 00:00', showTimeEnd: '2026-10-31 23:59', status: '1', - createTime: '2026-07-15 00:30:00', + createDate: '2026-07-15 00:30:00', }, { id: '7', img: 'https://picsum.photos/seed/banner7/80/44', title: '全民健身日特别活动', type: '1', - linkUrl: '', + params: 'EV20260701007', area: '北京市', showTimeBegin: '2026-08-08 00:00', showTimeEnd: '2026-08-08 23:59', status: '1', - createTime: '2026-07-14 03:50:00', + createDate: '2026-07-14 03:50:00', }, { id: '8', img: 'https://picsum.photos/seed/banner8/80/44', title: '新春运动季大促销', type: '2', - linkUrl: 'https://example.com/spring', + params: 'https://example.com/spring', area: '上海市', showTimeBegin: '2026-01-15 00:00', showTimeEnd: '2026-02-15 23:59', status: '0', - createTime: '2026-07-13 07:05:00', + createDate: '2026-07-13 07:05:00', }, { id: '9', img: 'https://picsum.photos/seed/banner9/80/44', title: '2026全国青少年羽毛球锦标赛火热报名中', - type: '1', - linkUrl: '', - area: '广东省深圳市', + type: '0', + params: '', + area: '全国', showTimeBegin: '2026-08-01 00:00', showTimeEnd: '2026-08-31 23:59', status: '1', - createTime: '2026-07-12 10:25:00', + createDate: '2026-07-12 10:25:00', }, { id: '10', img: 'https://picsum.photos/seed/banner10/80/44', title: '平台品牌宣传活动', type: '2', - linkUrl: 'https://example.com/brand', - area: '浙江省杭州市', + params: 'https://example.com/brand', + area: '杭州市', showTimeBegin: '2026-11-01 00:00', showTimeEnd: '2026-11-30 23:59', status: '1', - createTime: '2026-07-11 13:40:00', + createDate: '2026-07-11 13:40:00', }, { id: '11', img: 'https://picsum.photos/seed/banner11/80/44', title: '秋季健身挑战赛', type: '1', - linkUrl: '', + params: 'EV20260701011', area: '北京市', showTimeBegin: '2026-09-01 00:00', showTimeEnd: '2026-09-30 23:59', status: '1', - createTime: '2026-07-10 17:00:00', + createDate: '2026-07-10 17:00:00', }, { id: '12', img: 'https://picsum.photos/seed/banner12/80/44', title: '双十一运动装备大促', type: '2', - linkUrl: 'https://example.com/double11', + params: 'https://example.com/double11', area: '上海市', showTimeBegin: '2026-11-01 00:00', showTimeEnd: '2026-11-11 23:59', status: '1', - createTime: '2026-07-09 20:15:00', + createDate: '2026-07-09 20:15:00', }, ]; diff --git a/src/pages/events/banner/components/BannerFormModal.tsx b/src/pages/events/banner/components/BannerFormModal.tsx index 720971a..dc329fd 100644 --- a/src/pages/events/banner/components/BannerFormModal.tsx +++ b/src/pages/events/banner/components/BannerFormModal.tsx @@ -1,4 +1,4 @@ -import { defineComponent, ref, reactive } from 'vue'; +import { defineComponent, ref, reactive, computed } from 'vue'; import { useEffect, useState } from '@/hooks'; import { Modal, @@ -12,30 +12,30 @@ import { message, Spin, } from 'ant-design-vue'; -import { JUMP_TYPE_OPTIONS, EVENT_OPTIONS } from '../model/useBannerModel'; -import { - getProvinceCityCascaderOptions, - parseAreaToCascaderPath, - formatCascaderArea, -} from '@/utils/areaData'; +import { JUMP_TYPE_OPTIONS } from '../model/useBannerModel'; +import { getProvinceCityCascaderOptions, cityNameToCascaderPath } from '@/utils/areaData'; import { ImageCropper } from '@yp-component/root'; import { FileImageOutlined } from '@ant-design/icons-vue'; import { uploadFile, OssUploadType } from '@/utils/oss'; +import { getEventList, getEventDetail, type TournamentAdminVO } from '../../list/model/services'; import dayjs from 'dayjs'; import styles from './BannerFormModal.module.less'; -/** 省市两级 Cascader 选项 */ -const cascaderAreaOptions = getProvinceCityCascaderOptions(); +/** 所属区域 Cascader 选项:全国 + 省市两级 */ +const areaCascaderOptions: any[] = [ + { value: '全国', label: '全国' }, + ...getProvinceCityCascaderOptions(), +]; /** 默认表单数据 */ const getDefaultForm = () => ({ title: '', imageUrl: '', imageStatus: 'empty' as 'empty' | 'uploading' | 'success' | 'error', - type: '1' as '1' | '2' | '3', + type: '1' as '1' | '2' | '3' | '0', linkUrl: '', relatedEventId: undefined as string | undefined, - area: '', + area: '全国', displayTimeRange: null as [string, string] | null, sort: 1, }); @@ -47,8 +47,6 @@ const URL_MAX = 500; /** 图片上传限制:最大 10MB */ const IMAGE_MAX_SIZE = 10 * 1024 * 1024; -/** 裁剪比例:1053:351 = 3:1 */ -const CROP_ASPECT_RATIO = 1053 / 351; interface BannerFormModalProps { visible: boolean; @@ -72,6 +70,37 @@ function base64ToFile(dataUrl: string, fileName: string): File { return new File([u8arr], fileName, { type: mime }); } +// ============================================================ +// 工具函数 +// ============================================================ + +/** 从 Cascader 路径中提取城市名(最后一级),用于区域筛选 */ +function extractCityName(path: string[]): string { + if (!path || path.length === 0 || path[0] === '全国') return ''; + return path[path.length - 1]; +} + +/** 从 Cascader 路径中提取存储用的区域字符串 */ +function extractAreaValue(path: string[]): string { + if (!path || path.length === 0) return '全国'; + if (path[0] === '全国') return '全国'; + // 直辖市路径如 ["北京市"],普通城市如 ["广东省", "深圳市"] + return path[path.length - 1]; +} + +/** 根据跳转类型计算 parms 字段的值 */ +function buildParams(type: string, relatedEventId: string | undefined, linkUrl: string): string { + if (type === '1') return relatedEventId ?? ''; + if (type === '2') return linkUrl || ''; + if (type === '3') return linkUrl || ''; + // type === '0' 无链接 + return ''; +} + +// ============================================================ +// 组件 +// ============================================================ + /** * Banner 新增/编辑表单弹窗 */ @@ -88,36 +117,165 @@ export default defineComponent({ const formRef = ref(); const fileInputRef = ref(null); const formData = reactive(getDefaultForm()); - /** Cascader 区域的路径数组(与 formData.area 字符串双向同步) */ - const areaPath = ref([]); - /** 是否由用户主动切换了跳转类型(跳过初始加载的 type 联动校验) */ + + /** Cascader 区域的路径数组:如 ["全国"]、["广东省", "深圳市"] */ + const areaPath = ref(['全国']); + + /** 是否由用户主动切换了跳转类型 */ const typeChanged = ref(false); - /** 裁剪弹窗状态 */ + // ---- 裁剪弹窗状态 ---- const [cropperVisible, setCropperVisible] = useState(false); - /** 待裁剪图片的本地预览 URL */ const [uploadImageUrl, setUploadImageUrl] = useState(''); - /** 根据 record 初始化表单 */ + // ---- 赛事搜索状态 ---- + const eventKeyword = ref(''); + const [eventOptions, setEventOptions] = useState<{ value: string; label: string }[]>([]); + const [eventSearchLoading, setEventSearchLoading] = useState(false); + /** 编辑时加载赛事详情的独立 loading */ + const [editLoading, setEditLoading] = useState(false); + /** 手动防抖计时器 */ + let searchTimer: ReturnType | null = null; + + /** 赛事下拉选项:第一项固定为"不关联" */ + const eventSelectOptions = computed(() => { + return [{ value: '', label: '不关联' }, ...eventOptions.value]; + }); + + // ---- 赛事搜索 API 调用 ---- + const doSearch = async (keyword: string) => { + if (!keyword.trim()) { + setEventOptions([]); + return; + } + setEventSearchLoading(true); + try { + const cityName = extractCityName(areaPath.value); + const params: Record = { + page: '1', + limit: '999', + name: keyword.trim(), + }; + if (cityName) { + params.areaName = cityName; + } + const res = await getEventList(params as any); + if (res.code == 200) { + const list: TournamentAdminVO[] = res.data?.list || []; + setEventOptions( + list.map((item) => ({ + value: item.id, + label: item.name, + })), + ); + } else { + setEventOptions([]); + } + } catch (e) { + console.error('[Banner] 搜索赛事失败:', e); + setEventOptions([]); + } finally { + setEventSearchLoading(false); + } + }; + + /** 赛事搜索输入(带 350ms 手动防抖) */ + const onEventSearch = (value: string) => { + eventKeyword.value = value; + if (searchTimer) clearTimeout(searchTimer); + if (!value.trim()) { + setEventOptions([]); + return; + } + searchTimer = setTimeout(() => { + doSearch(value); + }, 350); + }; + + /** 所属区域 Cascader 变化 */ + const onAreaChange = (val: any) => { + const path: string[] = val || []; + const newArea = extractAreaValue(path); + areaPath.value = path; + formData.area = newArea; + + // 切换到非"全国"区域时清空已关联赛事 + if (newArea !== '全国') { + formData.relatedEventId = undefined; + formRef.value?.clearValidate('relatedEventId'); + } + + // 区域变化时重新搜索(如果当前有关键词) + if (eventKeyword.value.trim()) { + if (searchTimer) clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + doSearch(eventKeyword.value); + }, 0); + } + }; + + // ---- 根据 record 初始化表单 ---- const initFormFromRecord = (record: any) => { const fresh = getDefaultForm(); if (!record) { Object.assign(formData, fresh); - areaPath.value = []; + areaPath.value = ['全国']; + eventKeyword.value = ''; + setEventOptions([]); return; } - const area = record.area || record.region || ''; - areaPath.value = parseAreaToCascaderPath(area); + // 区域:兼容旧格式 "广东省-深圳市"、新格式 "深圳市"、"全国" + const rawArea = record.area || record.region || ''; + let cityName: string; + if (rawArea.includes('-')) { + cityName = rawArea.split('-').pop() || '全国'; + } else { + cityName = rawArea || '全国'; + } + areaPath.value = cityNameToCascaderPath(cityName); + + // 跳转类型 + const type = String(record.type || record.jumpType || '1'); + + // 关联赛事ID(编辑时从 params/linkUrl 读取) + let relatedEventId: string | undefined; + if (type === '1') { + const eventId = record.params || record.linkUrl || record.jumpUrl || ''; + if (eventId) { + relatedEventId = eventId; + // 异步获取赛事名称用于回显 + setEditLoading(true); + getEventDetail(eventId) + .then((detailRes) => { + if (detailRes.code == 200) { + const eventName = detailRes.data?.name || eventId; + setEventOptions([{ value: eventId, label: eventName }]); + } + }) + .catch(() => { + setEventOptions([{ value: eventId, label: eventId }]); + }) + .finally(() => { + setEditLoading(false); + }); + } + } + + eventKeyword.value = ''; + if (type !== '1' || !relatedEventId) { + setEventOptions([]); + } + Object.assign(formData, { ...fresh, title: record.title || '', imageUrl: record.img || record.imageUrl || '', imageStatus: record.img || record.imageUrl ? ('success' as const) : ('empty' as const), - type: String(record.type || record.jumpType || '1'), - linkUrl: record.linkUrl || record.jumpUrl || '', - relatedEventId: record.relatedEventId || undefined, - area, + type, + linkUrl: record.params || record.linkUrl || record.jumpUrl || '', + relatedEventId, + area: cityName, displayTimeRange: (record.showTimeBegin || record.displayStartTime) && (record.showTimeEnd || record.displayEndTime) @@ -130,10 +288,9 @@ export default defineComponent({ }); }; - /** 监听 visible 变化重置表单 */ + // ---- 监听 visible 变化重置表单 ---- useEffect(() => { if (props.visible) { - // 标记首次渲染,跳过 type 联动校验 typeChanged.value = false; initFormFromRecord(props.record); setCropperVisible(false); @@ -142,9 +299,16 @@ export default defineComponent({ } }, [() => props.visible]); - /** 监听 type 变化时清理联动字段值和校验 */ + // ---- 清理防抖计时器 ---- + useEffect(() => { + if (!props.visible && searchTimer) { + clearTimeout(searchTimer); + searchTimer = null; + } + }, [() => props.visible]); + + // ---- 监听 type 变化,清理联动字段 ---- useEffect(() => { - // 首次加载跳过校验 if (!typeChanged.value) { if (formData.type !== '1') formData.relatedEventId = undefined; if (formData.type !== '2' && formData.type !== '3') formData.linkUrl = ''; @@ -162,25 +326,28 @@ export default defineComponent({ const activeNames: string[] = []; if (formData.type === '1') activeNames.push('relatedEventId'); if (formData.type === '2' || formData.type === '3') activeNames.push('linkUrl'); - // 清除所有联动字段的校验状态 formRef.value?.clearValidate(['relatedEventId', 'linkUrl']); - // 如果有已填值,重新校验当前字段 if (activeNames.length) { setTimeout(() => formRef.value?.validateFields(activeNames).catch(() => {}), 0); } }, [() => formData.type]); - /** 触发隐藏的文件选择器 */ + /** 重新校验 Banner 图片字段,上传完成后清除或更新错误提示 */ + const revalidateImageUrl = () => { + setTimeout(() => { + formRef.value?.validateFields(['imageUrl']).catch(() => {}); + }, 0); + }; + + // ---- 图片上传相关 ---- const triggerFilePicker = () => { if (formData.imageStatus === 'uploading') return; fileInputRef.value?.click(); }; - /** 处理文件选择 → 打开裁剪弹窗 */ const handleFileChange = (e: Event) => { const target = e.target as HTMLInputElement; const file = target.files?.[0]; - // 重置 input 以便同一文件可再次选择 target.value = ''; if (!file) return; @@ -193,21 +360,17 @@ export default defineComponent({ return; } - // 生成本地预览 URL,打开裁剪弹窗 setUploadImageUrl(URL.createObjectURL(file)); setCropperVisible(true); }; - /** 裁剪确认回调:将裁剪后的图片上传到 OSS */ const handleCropConfirm = async (dataUrl: string) => { setCropperVisible(false); - // 释放旧的本地预览 if (formData.imageUrl && formData.imageUrl.startsWith('blob:')) { URL.revokeObjectURL(formData.imageUrl); } - // 先用 base64 做本地预览 const localUrl = dataUrl; formData.imageUrl = localUrl; formData.imageStatus = 'uploading'; @@ -216,27 +379,26 @@ export default defineComponent({ const file = base64ToFile(dataUrl, `banner_${Date.now()}.jpg`); const result = await uploadFile(file, OssUploadType.Banner as any); - // 释放本地预览 URL.revokeObjectURL(localUrl); formData.imageUrl = result.url; formData.imageStatus = 'success'; message.success('图片上传成功'); + revalidateImageUrl(); } catch (err: any) { console.error('[Banner] 上传失败:', err); URL.revokeObjectURL(localUrl); formData.imageUrl = ''; formData.imageStatus = 'error'; message.error(err?.message || '图片上传失败'); + revalidateImageUrl(); } - // 清理裁剪资源 if (uploadImageUrl.value) { URL.revokeObjectURL(uploadImageUrl.value); } setUploadImageUrl(''); }; - /** 裁剪弹窗取消 */ const onCropperCancel = () => { setCropperVisible(false); if (uploadImageUrl.value) { @@ -245,16 +407,16 @@ export default defineComponent({ setUploadImageUrl(''); }; - /** 删除已上传图片 */ const handleRemoveImage = () => { if (formData.imageUrl && formData.imageUrl.startsWith('blob:')) { URL.revokeObjectURL(formData.imageUrl); } formData.imageUrl = ''; formData.imageStatus = 'empty'; + revalidateImageUrl(); }; - /** 提交表单 */ + // ---- 提交表单 ---- const handleSubmit = async () => { try { await formRef.value?.validate(); @@ -271,15 +433,8 @@ export default defineComponent({ img: formData.imageUrl || '', title: formData.title || '', type: formData.type, - linkUrl: - formData.type === '1' - ? formData.relatedEventId || '' - : formData.type === '2' - ? formData.linkUrl || '' - : formData.type === '3' - ? formData.linkUrl || '' - : '', - area: formData.area || formatCascaderArea(areaPath.value), + params: buildParams(formData.type, formData.relatedEventId, formData.linkUrl), + area: formData.area, showTimeBegin: formData.displayTimeRange?.[0] || '', showTimeEnd: formData.displayTimeRange?.[1] || '', sort: String(formData.sort ?? 1), @@ -299,246 +454,295 @@ export default defineComponent({ centered wrapClassName={styles.bannerFormModalMain} > -
- {/* ===== Banner 标题 ===== */} - - - - - {/* ===== 上传图片(必填,带裁剪) ===== */} - { - if (!formData.imageUrl || formData.imageUrl.startsWith('blob:')) { - return Promise.reject(new Error('请上传 Banner 图片')); - } - return Promise.resolve(); - }, - }, - ]} - > -
- - {formData.imageStatus === 'empty' || formData.imageStatus === 'error' ? ( -
- - - - 点击上传 - - 建议尺寸 1053*351 像素 -
- 支持 JPG/PNG,最大 10MB -
-
- ) : ( -
- - banner preview - -
- 重新上传 -
-
- )} -
-
- - {/* ===== 跳转类型 ===== */} - - - - )} - - {formData.type === '2' && ( - - - - )} - - {formData.type === '3' && ( - - - - )} - - {/* ===== 所属区域 ===== */} - - { - formData.area = formatCascaderArea(val || []); - }} - /> - - - {/* ===== 展示时间(开始/结束) ===== */} -
- - { - formData.displayTimeRange = [ - v ? v.format('YYYY-MM-DD') : '', - formData.displayTimeRange?.[1] || '', - ]; - }} - style={{ width: '100%' }} - /> - - - { - const [s, e] = formData.displayTimeRange || []; - if (s && e && new Date(e) < new Date(s)) { - return Promise.reject(new Error('结束时间不能早于开始时间')); - } - return Promise.resolve(); - }, - }, - ]} - > - { - formData.displayTimeRange = [ - formData.displayTimeRange?.[0] || '', - v ? v.format('YYYY-MM-DD') : '', - ]; - }} - style={{ width: '100%' }} - /> - + {editLoading.value ? ( +
+
+ ) : ( + <> + + {/* ===== Banner 标题 ===== */} + + + - {/* ===== 排序 ===== */} - - - - + {/* ===== 上传图片(必填,带裁剪) ===== */} + { + if (formData.imageStatus === 'uploading') { + return Promise.reject(new Error('图片仍在上传中,请稍候')); + } + if ( + formData.imageUrl?.startsWith('blob:') || + formData.imageUrl?.startsWith('data:') + ) { + return Promise.reject(new Error('请等待图片上传完成')); + } + return Promise.resolve(); + }, + }, + ]} + > +
+ + {formData.imageStatus === 'empty' || formData.imageStatus === 'error' ? ( +
+ + + + 点击上传 + + 建议尺寸 1053*351 像素 +
+ 支持 JPG/PNG,最大 10MB +
+
+ ) : ( +
+ + banner preview + +
+ 重新上传 +
+
+ )} +
+
- {/* ===== 底部按钮 ===== */} -
- - -
+ {/* ===== 跳转类型 ===== */} + + { + if (formData.relatedEventId && eventOptions.value.length === 0) { + doSearch(formData.relatedEventId); + } + }} + /> + + )} + + {/* ===== 联动字段:自定义链接 ===== */} + {formData.type === '2' && ( + + + + )} + + {/* ===== 联动字段:小程序页面 ===== */} + {formData.type === '3' && ( + + + + )} + + {/* ===== 所属区域(省市树种选择 + 全国) ===== */} + + + + + {/* ===== 展示时间(开始/结束) ===== */} +
+ { + if (!formData.displayTimeRange?.[0]) { + return Promise.reject(new Error('请选择开始时间')); + } + return Promise.resolve(); + }, + }, + ]} + > + { + const newStart = v ? v.format('YYYY-MM-DD HH:mm:ss') : ''; + const currentEnd = formData.displayTimeRange?.[1] || ''; + if (newStart && currentEnd && dayjs(newStart).isAfter(dayjs(currentEnd))) { + formData.displayTimeRange = [currentEnd, newStart]; + } else { + formData.displayTimeRange = [newStart, currentEnd]; + } + formRef.value?.clearValidate(['displayStartTime', 'displayEndTime']); + }} + style={{ width: '100%' }} + /> + + + { + if (!formData.displayTimeRange?.[1]) { + return Promise.reject(new Error('请选择结束时间')); + } + return Promise.resolve(); + }, + }, + ]} + > + { + const newEnd = v ? v.format('YYYY-MM-DD HH:mm:ss') : ''; + const currentStart = formData.displayTimeRange?.[0] || ''; + if (newEnd && currentStart && dayjs(newEnd).isBefore(dayjs(currentStart))) { + formData.displayTimeRange = [newEnd, currentStart]; + } else { + formData.displayTimeRange = [currentStart, newEnd]; + } + formRef.value?.clearValidate(['displayStartTime', 'displayEndTime']); + }} + style={{ width: '100%' }} + /> + +
+ + {/* ===== 排序 ===== */} + + + + + + {/* ===== 底部按钮 ===== */} +
+ + +
+ + )} {/* ===== 图片裁剪弹窗 ===== */} @@ -555,7 +759,6 @@ export default defineComponent({
new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/pages/events/banner/model/useBannerModel.ts b/src/pages/events/banner/model/useBannerModel.ts index 85aa6ee..229d2c5 100644 --- a/src/pages/events/banner/model/useBannerModel.ts +++ b/src/pages/events/banner/model/useBannerModel.ts @@ -11,7 +11,6 @@ import { type TournamentAdminBannerListVO, type BannerListQueryParams, } from './services'; -import { getProvinceCityOptions } from '@/utils/areaData'; // ============================================================ // 常量 @@ -24,15 +23,17 @@ export const BANNER_STATUS_OPTIONS = [ { value: '0', label: '禁用' }, ] as const; -/** 跳转类型选项(value 对应 API type: 1=赛事详情,2=自定义连接,3=小程序页面) */ +/** 跳转类型选项(value 对应 API type: 0=无链接,1=赛事详情,2=自定义连接,3=小程序页面) */ export const JUMP_TYPE_OPTIONS = [ { value: '1', label: '赛事详情页' }, { value: '2', label: '自定义链接' }, { value: '3', label: '小程序页面' }, + { value: '0', label: '无链接' }, ] as const; /** 跳转类型映射 */ const JUMP_TYPE_MAP: Record = { + '0': { label: '无链接', tone: 'default' }, '1': { label: '赛事详情页', tone: 'primary' }, '2': { label: '自定义链接', tone: 'purple' }, '3': { label: '小程序页面', tone: 'cyan' }, @@ -44,18 +45,6 @@ const STATUS_MAP: Record
diff --git a/src/pages/events/list/index.tsx b/src/pages/events/list/index.tsx index f0967c8..eb2a026 100644 --- a/src/pages/events/list/index.tsx +++ b/src/pages/events/list/index.tsx @@ -21,6 +21,7 @@ import { import { useEventListColumns } from './model/useEventListColumns'; import { getProvinceCityCascaderOptions } from '@/utils/areaData'; import { useContainerSize } from '@/hooks'; +import { usePermissionStore } from '@/stores/permissionStore'; import EventRegulationModal from './components/EventRegulationModal'; import EventDetailModal from './components/EventDetailModal'; import EventAuditModal from './components/EventAuditModal'; @@ -63,6 +64,9 @@ export default defineComponent({ const { columns } = useEventListColumns(); const { containerRef, height } = useContainerSize(); + const { hasPermission } = usePermissionStore(); + const canToggleStatus = hasPermission('events.list.toggle_status'); + const canViewRegulation = hasPermission('events.regulation.view'); /** 省市两级 Cascader 数据(无区) */ const provinceCityOptions = getProvinceCityCascaderOptions(); @@ -91,14 +95,16 @@ export default defineComponent({ - + {canToggleStatus && ( + + )} ); } @@ -109,14 +115,16 @@ export default defineComponent({ - + {canToggleStatus && ( + + )} ); } @@ -145,9 +153,15 @@ export default defineComponent({ width: 100, align: 'center' as const, customRender: ({ record }: { record: any }) => ( - openRegulationModal(record)}> - 查看 - + <> + {canViewRegulation ? ( + openRegulationModal(record)}> + 查看 + + ) : ( + - + )} + ), }, { diff --git a/src/pages/events/orders/components/OrderDetailModal.tsx b/src/pages/events/orders/components/OrderDetailModal.tsx index 2166e7e..1bb44f4 100644 --- a/src/pages/events/orders/components/OrderDetailModal.tsx +++ b/src/pages/events/orders/components/OrderDetailModal.tsx @@ -17,6 +17,7 @@ interface OrderDetailModalProps { uniqueId: string; onClose: () => void; onReRefund: () => void; + canReRefund?: boolean; } const GENDER_MAP: Record = { '1': '男', '2': '女' }; @@ -118,6 +119,7 @@ export default defineComponent({ uniqueId: { type: String, default: '' }, onClose: { type: Function, default: null }, onReRefund: { type: Function, default: null }, + canReRefund: { type: Boolean, default: false }, }, setup(props: OrderDetailModalProps) { const [detail, setDetail] = useState(null); @@ -360,7 +362,7 @@ export default defineComponent({
{/* 只有存在退款失败记录时才显示重新退款按钮 */} - {hasRefundFailed.value ? ( + {props.canReRefund && hasRefundFailed.value ? ( diff --git a/src/pages/events/orders/index.tsx b/src/pages/events/orders/index.tsx index e9c51d3..5a97b67 100644 --- a/src/pages/events/orders/index.tsx +++ b/src/pages/events/orders/index.tsx @@ -15,6 +15,7 @@ import { import { useOrderModel, ORDER_STATUS_OPTIONS, REFUND_STATUS_OPTIONS } from './model/useOrderModel'; import { retryRefundOrder } from './model/services'; import { useContainerSize, useState } from '@/hooks'; +import { usePermissionStore } from '@/stores/permissionStore'; import OrderDetailModal from './components/OrderDetailModal'; import pageStyles from '@/assets/styles/pageLayout.module.less'; @@ -58,12 +59,14 @@ function renderBodyCell({ record, onView, onReRefund, + canReRefund, }: { column: any; text: any; record: any; onView: (record: any) => void; onReRefund: (record: any) => void; + canReRefund: boolean; }) { // 赛事名称:最多15字符,超出显示 Tooltip if (column.dataIndex === 'name') { @@ -82,7 +85,7 @@ function renderBodyCell({ if (column.key === 'action') { // 退款状态:1=无退款,2=退款成功,3=退款失败,-1=其他 // 只有退款失败(3)时显示重新退款按钮 - const showReRefund = record.refundStatus === '3'; + const showReRefund = canReRefund && record.refundStatus === '3'; return (
diff --git a/src/pages/events/users/index.tsx b/src/pages/events/users/index.tsx index 39d2994..9548cc8 100644 --- a/src/pages/events/users/index.tsx +++ b/src/pages/events/users/index.tsx @@ -3,6 +3,7 @@ import { Button, Input, Table, Form, Space, Select, Pagination } from 'ant-desig import { useUserModel, USER_STATUS_OPTIONS } from './model/useUserModel'; import { useUserColumns } from './model/useUserColumns'; import { useContainerSize } from '@/hooks'; +import { usePermissionStore } from '@/stores/permissionStore'; import pageStyles from '@/assets/styles/pageLayout.module.less'; /** @@ -30,9 +31,13 @@ export default defineComponent({ const { columns } = useUserColumns(); const { containerRef, height } = useContainerSize(); + const { hasPermission } = usePermissionStore(); + const canToggleStatus = hasPermission('events.users.toggle_status'); /** 渲染操作列 */ const renderAction = (record: any) => { + if (!canToggleStatus) return -; + const isActive = record.status === '1'; return (
diff --git a/src/pages/finance/withdraw/index.tsx b/src/pages/finance/withdraw/index.tsx index e0aacd8..bae8d92 100644 --- a/src/pages/finance/withdraw/index.tsx +++ b/src/pages/finance/withdraw/index.tsx @@ -2,6 +2,7 @@ import { defineComponent } from 'vue'; import { Button, Input, Table, Form, Space, Pagination, Select } from 'ant-design-vue'; import { useWithdrawModel, renderAuditStatus, renderPayStatus } from './model/useWithdrawModel'; import { useContainerSize } from '@/hooks'; +import { usePermissionStore } from '@/stores/permissionStore'; import WithdrawDetailModal from './components/WithdrawDetailModal'; import pageStyles from '@/assets/styles/pageLayout.module.less'; @@ -13,12 +14,14 @@ function renderBodyCell({ record, onAudit, onView, + canAudit, }: { column: any; text: any; record: any; onAudit: (record: any) => void; onView: (record: any) => void; + canAudit: boolean; }) { if (column.key === 'auditStatus') { return renderAuditStatus(record.auditStatus); @@ -28,7 +31,7 @@ function renderBodyCell({ } if (column.key === 'action') { // auditStatus=0(待审核)→ 显示「审核」,其他 → 显示「查看」 - if (record.auditStatus === 0) { + if (canAudit && record.auditStatus === 0) { return ( - + {canEdit && ( + + )} + {canDelete && ( + + )} ); } @@ -100,6 +113,12 @@ export default defineComponent({ } = useRoleModel(); const { containerRef, height } = useContainerSize(); + const { hasPermission } = usePermissionStore(); + const canAdd = hasPermission('system.roles.add'); + const operationPermissions = { + canEdit: hasPermission('system.roles.edit'), + canDelete: hasPermission('system.roles.delete'), + }; const triggerDelete = (record: any) => confirmDelete({ record, onOk: handleDelete }); @@ -135,9 +154,11 @@ export default defineComponent({ - + {canAdd && ( + + )} @@ -160,6 +181,7 @@ export default defineComponent({ onViewUsers: handleViewUsers, onEdit: handleEdit, onDelete: triggerDelete, + permissions: operationPermissions, }), }} diff --git a/src/pages/system/users/index.tsx b/src/pages/system/users/index.tsx index c3d476b..7a152ce 100644 --- a/src/pages/system/users/index.tsx +++ b/src/pages/system/users/index.tsx @@ -13,6 +13,7 @@ import { import { useUserModel, USER_STATUS_OPTIONS } from './model/useUserModel'; import { toggleUserActive, saveUser, updateUser } from './model/services'; import { useState, useThrottleFn, useContainerSize } from '@/hooks'; +import { usePermissionStore } from '@/stores/permissionStore'; import UserFormModal from './components/UserFormModal'; import pageStyles from '@/assets/styles/pageLayout.module.less'; @@ -24,24 +25,36 @@ function renderBodyCell({ record, onEdit, onToggleStatus, + permissions, }: { column: any; text: any; record: any; onEdit: (record: any) => void; onToggleStatus: (record: any) => void; + permissions: { + canEdit: boolean; + canToggleStatus: boolean; + }; }) { // 操作列 if (column.key === 'action') { const isActive = record.status === '1'; + const { canEdit, canToggleStatus } = permissions; + if (!canEdit && !canToggleStatus) return -; + return ( - - + {canEdit && ( + + )} + {canToggleStatus && ( + + )} ); } @@ -67,6 +80,12 @@ export default defineComponent({ } = useUserModel(); const { containerRef, height } = useContainerSize(); + const { hasPermission } = usePermissionStore(); + const canAdd = hasPermission('system.users.add'); + const operationPermissions = { + canEdit: hasPermission('system.users.edit'), + canToggleStatus: hasPermission('system.users.toggle_status'), + }; // ===== 弹窗状态(组件级) ===== const [modalVisible, setModalVisible] = useState(false); @@ -216,9 +235,11 @@ export default defineComponent({ - + {canAdd && ( + + )} @@ -240,6 +261,7 @@ export default defineComponent({ ...args, onEdit: handleEdit, onToggleStatus: handleToggleStatus, + permissions: operationPermissions, }), }} diff --git a/src/pages/system/users/model/services.ts b/src/pages/system/users/model/services.ts index 0bba1a0..6f4b4d3 100644 --- a/src/pages/system/users/model/services.ts +++ b/src/pages/system/users/model/services.ts @@ -32,7 +32,7 @@ const userPage = '/admin/sys/user/page'; const userSave = '/admin/sys/user/save'; const userUpdate = '/admin/sys/user/update'; const userActive = '/admin/sys/user/active'; -const userUpdatePwd = '/sys/user/updatepwd'; +const userUpdatePwd = '/admin/sys/user/updatepwd'; // ============================================================ // API 函数 diff --git a/src/router/guard.ts b/src/router/guard.ts index b3a501b..1e60872 100644 --- a/src/router/guard.ts +++ b/src/router/guard.ts @@ -39,6 +39,11 @@ router.beforeEach(async (to, _from, next) => { await Promise.all([loadMenu(), loadPermissions()]); } + if (!auth.isLoggedIn()) { + next(); + return; + } + const redirectPath = getRedirectPath(to.query.redirect); const targetPath = redirectPath && isRoutePathAvailable(redirectPath) @@ -63,6 +68,12 @@ router.beforeEach(async (to, _from, next) => { const { loadPermissions } = usePermissionStore(); await Promise.all([loadMenu(), loadPermissions()]); + // 权限获取失败时会清空会话,此时回登录页而不是继续进入业务页 + if (!auth.isLoggedIn()) { + next({ path: '/login', replace: true }); + return; + } + // 动态路由刚注册,需要用新路由表重新匹配当前路径 next({ path: to.fullPath, replace: true }); return; diff --git a/src/stores/menuStore.ts b/src/stores/menuStore.ts index 5c29a3f..11f67d0 100644 --- a/src/stores/menuStore.ts +++ b/src/stores/menuStore.ts @@ -3,6 +3,7 @@ import type { RouteRecordRaw } from 'vue-router'; import type { MenuNode, MenuItemRaw } from '@/types'; import router, { resetRouter } from '@/router'; import { fetchMenuTree } from '@/api/menu'; +import { auth } from '@/hooks/useAuth'; import { pageModules } from '@/router/glob'; import { FALLBACK_MENU_NODES } from '@/config/fallbackRoutes'; @@ -276,6 +277,8 @@ export function useMenuStore() { state.loaded = true; } catch (err) { console.error('加载菜单失败:', err); + // 权限获取失败时会强制重新登录,此时不再降级兜底菜单,避免错误路由状态 + if (!auth.isLoggedIn()) return; // 降级:使用本地兜底菜单 applyMenuTree(FALLBACK_MENU_NODES); state.loaded = true; diff --git a/src/stores/permissionStore.ts b/src/stores/permissionStore.ts index 9a49ee3..f250eac 100644 --- a/src/stores/permissionStore.ts +++ b/src/stores/permissionStore.ts @@ -1,6 +1,7 @@ import { reactive, computed, toRefs } from 'vue'; import type { PermissionCode } from '@/types'; import { fetchAuthData } from '@/api/menu'; +import { auth } from '@/hooks/useAuth'; interface PermissionState { /** 权限编码集合 */ @@ -31,6 +32,7 @@ export function usePermissionStore() { state.loaded = true; } catch (err) { console.error('加载权限失败:', err); + if (!auth.isLoggedIn()) return; state.codes = new Set(); state.roles = []; state.loaded = true; diff --git a/src/utils/request.ts b/src/utils/request.ts index e386511..f89036f 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,4 +1,4 @@ -import { auth } from '@/hooks/'; +import { auth, forceReLogin } from '@/hooks/'; import { message } from 'ant-design-vue'; const baseUrl = import.meta.env?.VITE_API_BASE_URL || ''; @@ -8,9 +8,7 @@ const activeControllers = new Set(); /** 统一处理 401 未授权逻辑 */ const handleUnauthorized = (msg?: string) => { - auth.logout(); - message.error(msg || '登录状态已过期,请重新登录'); - window.location.href = '/login'; + forceReLogin(msg || '登录状态已过期,请重新登录'); }; const request = ( -- 2.52.0 From e79404aba90ef4a36da0aa0bedbc41586ecae2f8 Mon Sep 17 00:00:00 2001 From: ZhuRui Date: Wed, 5 Aug 2026 15:40:57 +0800 Subject: [PATCH 10/25] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/BasicLayout.tsx | 7 +++++-- src/pages/login/index.tsx | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index fc6064b..cd73a31 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -78,7 +78,7 @@ export default defineComponent({ const [collapsed, setCollapsed] = useState(false); const { menuItems } = useMenuStore(); - const { phone } = useUserStore(); + const { phone, nickname } = useUserStore(); const { tabs, cachedViews, @@ -138,6 +138,9 @@ export default defineComponent({ if (res.code == 200) { message.success('密码修改成功'); setChangePwdVisible(false); + window.setTimeout(() => { + auth.logout(); + }, 1500); } else { message.error(res.msg || '修改失败'); } @@ -229,7 +232,7 @@ export default defineComponent({ {/* 右侧: 账号信息 + 退出 */}
- {phone.value} + {nickname.value} {{ default: () => , diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 176767a..481fd37 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -55,6 +55,11 @@ export default defineComponent({ try { const res = await loginApi({ phone: form.phone, password: form.password }); + if (res.code != 200) { + message.error(res.msg || '登录失败'); + return; + } + setUser({ phone: form.phone, nickname: res.data.nickname, token: res.data.token }); if (rememberMe.value) { -- 2.52.0 From b3458fd4b89ad094c8c0270b4d4cb931c5049d00 Mon Sep 17 00:00:00 2001 From: ZhuRui Date: Wed, 5 Aug 2026 16:02:01 +0800 Subject: [PATCH 11/25] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/login/index.ts | 9 ++++++++- src/api/login/types.ts | 8 ++++++++ src/api/menu.ts | 21 +++++++++++++++++--- src/layouts/BasicLayout.module.less | 2 +- src/layouts/BasicLayout.tsx | 3 ++- src/pages/login/index.tsx | 6 +++++- src/stores/userStore.ts | 30 +++++++++++++++++++++++++++-- 7 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 src/api/login/types.ts diff --git a/src/api/login/index.ts b/src/api/login/index.ts index ab552ec..1acfc8a 100644 --- a/src/api/login/index.ts +++ b/src/api/login/index.ts @@ -1,5 +1,12 @@ import { post } from '@/utils/request'; +import type { LoginResult } from './types'; + +export type { LoginResult } from './types'; export const login = (params: { phone: string; password: string }) => { - return post(`/op/login`, params); + return post(`/op/login`, params) as Promise<{ + code: number; + msg: string; + data: LoginResult; + }>; }; diff --git a/src/api/login/types.ts b/src/api/login/types.ts new file mode 100644 index 0000000..d152867 --- /dev/null +++ b/src/api/login/types.ts @@ -0,0 +1,8 @@ +export interface LoginResult { + token: string; + phone?: string; + nickName?: string; + realName?: string; + userName?: string; + name?: string; +} diff --git a/src/api/menu.ts b/src/api/menu.ts index 7c5d73c..f9be5b1 100644 --- a/src/api/menu.ts +++ b/src/api/menu.ts @@ -3,6 +3,7 @@ import type { MenuNode, PermissionCode } from '@/types'; import { FALLBACK_MENU_NODES } from '@/config/fallbackRoutes'; import { getAuthDataPromise, setAuthDataPromise, clearAuthDataCache } from '@/api/authCache'; import { forceReLogin } from '@/hooks/useAuth'; +import { useUserStore } from '@/stores/userStore'; export { clearAuthDataCache } from '@/api/authCache'; @@ -12,6 +13,8 @@ export { clearAuthDataCache } from '@/api/authCache'; export interface AuthData { roleList: string[]; permsList: string[]; + nickname: string; + phone: string; } const AUTH_FETCH_FAIL_MSG = '账户权限获取失败,请重新登录'; @@ -21,14 +24,26 @@ function normalizeAuthData(res: any): AuthData { throw new Error(res.msg || AUTH_FETCH_FAIL_MSG); } - const roleList = res?.data?.roleList || []; - const permsList = res?.data?.permsList || []; + const data = res?.data || {}; + const roleList = data.roleList || []; + const permsList = data.permsList || []; if (permsList.length === 0) { throw new Error(AUTH_FETCH_FAIL_MSG); } - return { roleList, permsList }; + const nickname = data.nickName || ''; + const phone = typeof data.phone === 'string' ? data.phone : ''; + + if (nickname || phone) { + const { setUser } = useUserStore(); + setUser({ + ...(nickname ? { nickname } : {}), + ...(phone ? { phone } : {}), + }); + } + + return { roleList, permsList, nickname, phone }; } function handleAuthFetchFailure() { diff --git a/src/layouts/BasicLayout.module.less b/src/layouts/BasicLayout.module.less index 2a25135..bf59f0e 100644 --- a/src/layouts/BasicLayout.module.less +++ b/src/layouts/BasicLayout.module.less @@ -62,7 +62,7 @@ align-items: center; } - .userPhone { + .displayName { font-size: 14px; color: rgba(0, 0, 0, 0.65); } diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index cd73a31..bcbd633 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -79,6 +79,7 @@ export default defineComponent({ const { menuItems } = useMenuStore(); const { phone, nickname } = useUserStore(); + const displayName = computed(() => nickname.value || phone.value); const { tabs, cachedViews, @@ -232,7 +233,7 @@ export default defineComponent({ {/* 右侧: 账号信息 + 退出 */}
- {nickname.value} + {displayName.value} {{ default: () => , diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 481fd37..8f1059c 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -60,7 +60,11 @@ export default defineComponent({ return; } - setUser({ phone: form.phone, nickname: res.data.nickname, token: res.data.token }); + setUser({ + phone: res.data.phone || form.phone, + nickname: res.data.nickName || '', + token: res.data.token, + }); if (rememberMe.value) { localStorage.setItem( diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts index 1fc5fc1..6d8074d 100644 --- a/src/stores/userStore.ts +++ b/src/stores/userStore.ts @@ -9,9 +9,32 @@ interface UserState { token: string; } +const USER_PROFILE_KEY = 'cpms_user_profile'; + +function loadUserProfile(): Pick { + try { + const raw = window.localStorage.getItem(USER_PROFILE_KEY); + if (!raw) return { phone: '', nickname: '' }; + const data = JSON.parse(raw); + return { + phone: typeof data.phone === 'string' ? data.phone : '', + nickname: typeof data.nickname === 'string' ? data.nickname : '', + }; + } catch { + window.localStorage.removeItem(USER_PROFILE_KEY); + return { phone: '', nickname: '' }; + } +} + +function persistUserProfile(profile: Pick) { + window.localStorage.setItem(USER_PROFILE_KEY, JSON.stringify(profile)); +} + +const savedProfile = loadUserProfile(); + const state = reactive({ - phone: '', - nickname: '', + phone: savedProfile.phone, + nickname: savedProfile.nickname, token: '', }); @@ -20,12 +43,15 @@ export function useUserStore() { if (user.phone !== undefined) state.phone = user.phone; if (user.nickname !== undefined) state.nickname = user.nickname; if (user.token !== undefined) state.token = user.token; + + persistUserProfile({ phone: state.phone, nickname: state.nickname }); }; const clearUser = () => { state.phone = ''; state.nickname = ''; state.token = ''; + window.localStorage.removeItem(USER_PROFILE_KEY); }; return { -- 2.52.0 From 8951733a2d92a1a99c75980e2983c3e78cb2d2d2 Mon Sep 17 00:00:00 2001 From: ZhuRui Date: Wed, 5 Aug 2026 16:28:44 +0800 Subject: [PATCH 12/25] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=E4=BD=99?= =?UTF-8?q?=E9=A2=9D=E6=96=B9=E9=9D=A2=E9=97=AE=E9=A2=98=E4=B8=8Ebanner?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=B8=8D=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/wallet/index.ts | 4 +- src/api/wallet/types.ts | 17 +++++- src/config/mock/walletList.ts | 58 +++---------------- .../events/banner/model/useBannerModel.ts | 25 ++++---- src/pages/finance/wallet/model/services.ts | 4 +- .../finance/wallet/model/useWalletModel.ts | 15 ++--- src/pages/finance/withdraw/index.tsx | 2 +- 7 files changed, 47 insertions(+), 78 deletions(-) diff --git a/src/api/wallet/index.ts b/src/api/wallet/index.ts index 9d605d5..4714102 100644 --- a/src/api/wallet/index.ts +++ b/src/api/wallet/index.ts @@ -9,6 +9,7 @@ import { buildMockWalletTransactionPage } from '@/config/mock/walletTransactions import type { WalletQueryParams, WalletSummaryVO, + WalletSummaryPageData, WalletTransactionQueryParams, WalletTransactionVO, PageData, @@ -17,6 +18,7 @@ import type { export type { WalletSummaryVO, + WalletSummaryPageData, WalletQueryParams, WalletTransactionVO, WalletTransactionQueryParams, @@ -29,7 +31,7 @@ const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); /** GET /admin/finance/wallet/summary — 钱包列表分页 */ export async function getWalletList( params: WalletQueryParams, -): Promise>> { +): Promise> { if (USE_MOCK) { await delay(MOCK_DELAY); const page = Number(params.page) || 1; diff --git a/src/api/wallet/types.ts b/src/api/wallet/types.ts index 5f993e9..a0dc2aa 100644 --- a/src/api/wallet/types.ts +++ b/src/api/wallet/types.ts @@ -17,14 +17,25 @@ export interface WalletSummaryVO { frozenAmount: string; /** 累计成功提现金额("0.00"格式) */ withdrawalAmount: string; - /** 汇总行-总余额 */ +} + +/** 钱包汇总扩展数据(与 total 同级) */ +export interface WalletSummaryExData { + /** 总余额 */ totalAmount: string; - /** 汇总行-总冻结金额 */ + /** 总冻结金额 */ totalFrozenAmount: string; - /** 汇总行-总累计提现 */ + /** 总累计提现 */ totalWithdrawalAmount: string; } +/** 钱包列表分页响应 */ +export interface WalletSummaryPageData { + total: number; + list: WalletSummaryVO[]; + exData: WalletSummaryExData; +} + /** 钱包列表查询参数 */ export interface WalletQueryParams { page?: string; diff --git a/src/config/mock/walletList.ts b/src/config/mock/walletList.ts index afd273c..c7b4a5a 100644 --- a/src/config/mock/walletList.ts +++ b/src/config/mock/walletList.ts @@ -2,7 +2,7 @@ * 钱包列表假数据 * 对应接口:GET /admin/finance/wallet/summary */ -import type { WalletSummaryVO, WalletQueryParams, PageData } from '@/api/wallet'; +import type { WalletSummaryVO, WalletQueryParams, WalletSummaryPageData } from '@/api/wallet'; const MOCK_LIST: WalletSummaryVO[] = [ { @@ -12,9 +12,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '0.00', frozenAmount: '0.00', withdrawalAmount: '0.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1002, @@ -23,9 +20,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '350.00', frozenAmount: '200.00', withdrawalAmount: '1450.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1003, @@ -34,9 +28,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '1280.00', frozenAmount: '0.00', withdrawalAmount: '800.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1004, @@ -45,9 +36,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '560.00', frozenAmount: '100.00', withdrawalAmount: '200.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1005, @@ -56,9 +44,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '4200.00', frozenAmount: '1500.00', withdrawalAmount: '5600.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1006, @@ -67,9 +52,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '880.00', frozenAmount: '0.00', withdrawalAmount: '300.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1007, @@ -78,9 +60,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '2100.00', frozenAmount: '800.00', withdrawalAmount: '3200.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1008, @@ -89,9 +68,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '650.00', frozenAmount: '0.00', withdrawalAmount: '450.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1009, @@ -100,9 +76,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '980.00', frozenAmount: '200.00', withdrawalAmount: '1200.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1010, @@ -111,9 +84,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '320.00', frozenAmount: '50.00', withdrawalAmount: '100.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1011, @@ -122,9 +92,6 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '5800.00', frozenAmount: '0.00', withdrawalAmount: '2000.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, { userId: 1012, @@ -133,14 +100,10 @@ const MOCK_LIST: WalletSummaryVO[] = [ amount: '75.00', frozenAmount: '0.00', withdrawalAmount: '75.00', - totalAmount: '0.00', - totalFrozenAmount: '0.00', - totalWithdrawalAmount: '0.00', }, ]; -/** 计算汇总值 */ -function computeSummary(list: WalletSummaryVO[]) { +function computeExData(list: WalletSummaryVO[]) { const totalAmount = list.reduce((s, i) => s + Number(i.amount || 0), 0).toFixed(2); const totalFrozenAmount = list.reduce((s, i) => s + Number(i.frozenAmount || 0), 0).toFixed(2); const totalWithdrawalAmount = list @@ -153,7 +116,7 @@ export function buildMockWalletPage( page: number, limit: number, params: WalletQueryParams, -): PageData { +): WalletSummaryPageData { let filtered = [...MOCK_LIST]; if (params.nickname) { @@ -171,15 +134,10 @@ export function buildMockWalletPage( const total = filtered.length; const start = (page - 1) * limit; - const pageList = filtered.slice(start, start + limit); - // 将汇总值写入每条记录(方便前端提取统计卡数据) - const summary = computeSummary(filtered); - for (const item of pageList) { - item.totalAmount = summary.totalAmount; - item.totalFrozenAmount = summary.totalFrozenAmount; - item.totalWithdrawalAmount = summary.totalWithdrawalAmount; - } - - return { total, list: pageList }; + return { + total, + list: filtered.slice(start, start + limit), + exData: computeExData(filtered), + }; } diff --git a/src/pages/events/banner/model/useBannerModel.ts b/src/pages/events/banner/model/useBannerModel.ts index 132c633..4f96cf1 100644 --- a/src/pages/events/banner/model/useBannerModel.ts +++ b/src/pages/events/banner/model/useBannerModel.ts @@ -64,13 +64,24 @@ export function useBannerModel() { { delay: 300 }, ); + // ===== 构建 API 查询参数 ===== + const buildQueryParams = (): BannerListQueryParams => { + const params: BannerListQueryParams = {}; + if (filterForm.title.trim()) { + params.title = filterForm.title.trim(); + } + if (filterForm.status) { + params.status = filterForm.status; + } + return params; + }; + // ===== 数据请求(API 返回全量,前端分页) ===== const { data, loading, run: fetchList, } = useRequest(() => getBannerList(buildQueryParams()), { - refreshDeps: [], formatResult: (res) => (res.code == 200 ? res.data : []), }); @@ -111,18 +122,6 @@ export function useBannerModel() { { title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center' as const }, ]; - // ===== 构建 API 查询参数 ===== - const buildQueryParams = (): BannerListQueryParams => { - const params: BannerListQueryParams = {}; - if (filterForm.title.trim()) { - params.title = filterForm.title.trim(); - } - if (filterForm.status) { - params.status = filterForm.status; - } - return params; - }; - // ===== 计算属性 ===== const hasFilter = computed(() => { return debouncedTitle.value.trim() !== '' || filterForm.status !== ''; diff --git a/src/pages/finance/wallet/model/services.ts b/src/pages/finance/wallet/model/services.ts index 9bcce65..13d90bd 100644 --- a/src/pages/finance/wallet/model/services.ts +++ b/src/pages/finance/wallet/model/services.ts @@ -9,6 +9,7 @@ import { buildMockWalletTransactionPage } from '@/config/mock/walletTransactions import type { WalletQueryParams, WalletSummaryVO, + WalletSummaryPageData, WalletTransactionQueryParams, WalletTransactionVO, PageData, @@ -17,6 +18,7 @@ import type { export type { WalletSummaryVO, + WalletSummaryPageData, WalletQueryParams, WalletTransactionVO, WalletTransactionQueryParams, @@ -39,7 +41,7 @@ const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); /** 钱包列表分页 */ export async function getWalletList( params: WalletQueryParams, -): Promise>> { +): Promise> { if (USE_MOCK) { await delay(MOCK_DELAY); const page = Number(params.page) || 1; diff --git a/src/pages/finance/wallet/model/useWalletModel.ts b/src/pages/finance/wallet/model/useWalletModel.ts index f98661e..9a63da1 100644 --- a/src/pages/finance/wallet/model/useWalletModel.ts +++ b/src/pages/finance/wallet/model/useWalletModel.ts @@ -102,15 +102,12 @@ export function useWalletModel() { const listData = computed(() => (data.value as any)?.data); const dataSource = computed(() => listData.value?.list || []); - // 汇总:从第一条记录提取 - const summary = computed(() => { - const first = dataSource.value?.[0] as any; - return { - totalBalance: first?.totalAmount ?? '0.00', - frozenAmount: first?.totalFrozenAmount ?? '0.00', - totalWithdraw: first?.totalWithdrawalAmount ?? '0.00', - }; - }); + // 汇总:从 exData 派生(与 list 同级) + const summary = computed(() => ({ + totalBalance: listData.value?.exData?.totalAmount ?? '0.00', + frozenAmount: listData.value?.exData?.totalFrozenAmount ?? '0.00', + totalWithdraw: listData.value?.exData?.totalWithdrawalAmount ?? '0.00', + })); // 同步 total 到 pagination useEffect(() => { diff --git a/src/pages/finance/withdraw/index.tsx b/src/pages/finance/withdraw/index.tsx index bae8d92..c21552c 100644 --- a/src/pages/finance/withdraw/index.tsx +++ b/src/pages/finance/withdraw/index.tsx @@ -31,7 +31,7 @@ function renderBodyCell({ } if (column.key === 'action') { // auditStatus=0(待审核)→ 显示「审核」,其他 → 显示「查看」 - if (canAudit && record.auditStatus === 0) { + if (canAudit && record.auditStatus == 0) { return (