Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab6f5c787e | |||
| 79a7ea8c56 | |||
| b5ba0ca3fb | |||
| 9b5e2807b5 | |||
| 3a30e81183 | |||
| 28386f5429 | |||
| b0b8689acb | |||
| 8012cb82fc | |||
| 73b2da2d1e | |||
| 6f8e5036f7 | |||
| daab8ec41a | |||
| b513d701d8 | |||
| f8eaa554b3 | |||
| 9cee70bc62 | |||
| ffd917815d | |||
| 3841fcc7e5 | |||
| ac7bfeacfe | |||
| d9a20c906b | |||
| 5300a1ec2e | |||
| 68820dfcea | |||
| d8ee7b3263 | |||
| 3f47005f76 |
+4
-1
@@ -1,2 +1,5 @@
|
||||
# 生产环境
|
||||
VITE_API_BASE_URL = https://api.cpms.example.com/admin
|
||||
VITE_API_BASE_URL = https://cpmsapi.yunvip123.com
|
||||
|
||||
# 是否开启本地调试
|
||||
LOCAL_DEBUG=false
|
||||
|
||||
@@ -6,10 +6,15 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"serve": "vite",
|
||||
"serve:prod": "vite --mode production",
|
||||
"build:dev": "vue-tsc --noEmit && vite build --mode development",
|
||||
"build:test": "vue-tsc --noEmit && vite build --mode test",
|
||||
"build:prod": "vue-tsc --noEmit && vite build --mode production",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"preview": "vite preview",
|
||||
"preview:prod": "pnpm build:prod && vite preview",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint src --ext .js,.ts,.tsx",
|
||||
"lint:fix": "eslint src --ext .js,.ts,.tsx --fix",
|
||||
@@ -42,17 +47,21 @@
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||
"@vitest/coverage-v8": "2.1.9",
|
||||
"@vue/test-utils": "^2.4.11",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^19.0.0",
|
||||
"less": "^4.6.4",
|
||||
"lint-staged": "^15.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-remove-console": "^2.2.0",
|
||||
"vitest": "^2.1.9",
|
||||
"vue-tsc": "^2.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# ============================================================
|
||||
# CPMS 运营平台 - 权限标识字符串列表
|
||||
# 生成规则:
|
||||
# 1. 每个页面都有一个 "页面名-查看" 的默认权限
|
||||
# 2. 表格操作列中的按钮生成对应操作权限
|
||||
# 3. 同一关键字的两个状态按钮(如上架/下架、启用/禁用)合并为一个权限
|
||||
# 4. 筛选栏中有"新增"按钮的页面,生成新增权限
|
||||
# 5. 标识为英文,附带中文说明(含按钮所在位置)
|
||||
# ============================================================
|
||||
|
||||
# ─── 赛事管理模块 ─────────────────────────────────────────
|
||||
|
||||
events.list.view # 赛事列表-查看(页面默认查看权限)
|
||||
events.list.toggle_status # 赛事列表-上架/下架(表格操作列,根据赛事状态显示"上架"或"下架"按钮,合并为一个权限)
|
||||
events.regulation.view # 赛事规程-查看(赛事列表表格中的"赛事规程"列,点击"查看"链接)
|
||||
|
||||
events.orders.view # 订单管理-查看(页面默认查看权限)
|
||||
events.orders.refund # 订单管理-重新退款(表格操作列,退款状态为3时显示"重新退款"按钮)
|
||||
|
||||
events.users.view # 用户列表-查看(页面默认查看权限)
|
||||
events.users.toggle_status # 用户列表-启用/禁用(表格操作列,根据用户状态显示"启用"或"禁用"按钮,合并为一个权限)
|
||||
|
||||
events.banner.view # Banner配置-查看(页面默认查看权限)
|
||||
events.banner.add # Banner配置-新增(筛选栏中的"新增"按钮)
|
||||
events.banner.edit # Banner配置-编辑(表格操作列中的"编辑"按钮)
|
||||
events.banner.toggle_status # Banner配置-启用/禁用(表格操作列,根据状态显示"启用"或"禁用"按钮,合并为一个权限)
|
||||
events.banner.delete # Banner配置-删除(表格操作列中的"删除"按钮,红色危险样式)
|
||||
|
||||
events.logs.view # 赛事操作日志-查看(页面默认查看权限,无操作列按钮)
|
||||
|
||||
# ─── 账务管理模块 ─────────────────────────────────────────
|
||||
|
||||
finance.withdraw.view # 提现申请-查看(页面默认查看权限)
|
||||
finance.withdraw.audit # 提现申请-审核(表格操作列,审核状态为"待审核"时显示"审核"按钮)
|
||||
|
||||
finance.wallet.view # 用户钱包-查看(页面默认查看权限)
|
||||
finance.wallet.detail # 用户钱包-查看明细(表格操作列中的"明细"按钮,打开交易明细弹窗)
|
||||
|
||||
finance.payments.view # 支付流水-查看(页面默认查看权限,无操作列按钮,仅展示数据+金额汇总)
|
||||
|
||||
finance.reports.view # 账务报表-查看(页面默认查看权限,无操作列按钮,展示统计卡+趋势图+明细表)
|
||||
|
||||
# ─── 系统管理模块 ─────────────────────────────────────────
|
||||
|
||||
system.users.view # 用户管理-查看(页面默认查看权限)
|
||||
system.users.add # 用户管理-新增(筛选栏中的"新增"按钮)
|
||||
system.users.edit # 用户管理-编辑(表格操作列中的"编辑"按钮)
|
||||
system.users.toggle_status # 用户管理-启用/禁用(表格操作列,根据用户状态显示"启用"或"禁用"按钮,合并为一个权限)
|
||||
|
||||
system.roles.view # 角色权限-查看(页面默认查看权限)
|
||||
system.roles.add # 角色权限-新增(筛选栏中的"新增角色"按钮)
|
||||
system.roles.edit # 角色权限-编辑(表格操作列中的"编辑"按钮)
|
||||
system.roles.delete # 角色权限-删除(表格操作列中的"删除"按钮,红色危险样式)
|
||||
|
||||
system.logs.view # 操作日志-查看(页面默认查看权限,无操作列按钮)
|
||||
|
||||
system.params.view # 参数配置-查看
|
||||
Generated
+1045
-1
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 参数配置 API
|
||||
*
|
||||
* TODO: 等后端接口确定后,替换占位的 URL 和返回类型
|
||||
*/
|
||||
import { get, post, put, del } from '@/utils/request';
|
||||
import type { ParamQueryParams, SysParamVO, PageData, ApiResult } from './types';
|
||||
|
||||
// 类型重导出,方便页面 model 层统一引用
|
||||
export type { SysParamVO, ParamQueryParams, PageData, ApiResult } from './types';
|
||||
|
||||
// ============================================================
|
||||
// URL 常量(占位,等后端接口确定后替换)
|
||||
// ============================================================
|
||||
|
||||
/** 配置列表分页查询 */
|
||||
const PARAM_LIST = '/admin/sys/param/page';
|
||||
/** 新增配置 */
|
||||
const PARAM_ADD = '/admin/sys/param/add';
|
||||
/** 编辑配置 */
|
||||
const PARAM_UPDATE = '/admin/sys/param/update';
|
||||
/** 删除配置 */
|
||||
const PARAM_DELETE = '/admin/sys/param/delete';
|
||||
|
||||
// ============================================================
|
||||
// API 函数(占位)
|
||||
// ============================================================
|
||||
|
||||
/** 配置列表分页 */
|
||||
export async function getParamList(
|
||||
params: ParamQueryParams,
|
||||
): Promise<ApiResult<PageData<SysParamVO>>> {
|
||||
return get(PARAM_LIST, params as Record<string, any>);
|
||||
}
|
||||
|
||||
/** 新增配置 */
|
||||
export async function addParam(data: Partial<SysParamVO>): Promise<ApiResult<null>> {
|
||||
return post(PARAM_ADD, data);
|
||||
}
|
||||
|
||||
/** 编辑配置 */
|
||||
export async function updateParam(data: Partial<SysParamVO>): Promise<ApiResult<null>> {
|
||||
return put(PARAM_UPDATE, data);
|
||||
}
|
||||
|
||||
/** 删除配置 */
|
||||
export async function deleteParam(id: number): Promise<ApiResult<null>> {
|
||||
return del(`${PARAM_DELETE}`, { id });
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 参数配置 — 类型定义
|
||||
*
|
||||
* TODO: 等后端接口确定后,根据实际字段补充完整类型
|
||||
*/
|
||||
|
||||
/** 配置项列表项 */
|
||||
export interface SysParamVO {
|
||||
/** 配置项 ID */
|
||||
id: number;
|
||||
/** 配置项 key */
|
||||
// paramKey: string;
|
||||
/** 配置项 value */
|
||||
// paramValue: string;
|
||||
/** 备注 */
|
||||
// remark: string;
|
||||
/** 更新时间 */
|
||||
// updateTime: string;
|
||||
}
|
||||
|
||||
/** 配置列表查询参数 */
|
||||
export interface ParamQueryParams {
|
||||
page?: string;
|
||||
limit?: string;
|
||||
/** 搜索关键字 */
|
||||
// keyword?: string;
|
||||
}
|
||||
|
||||
/** 分页数据结构 */
|
||||
export interface PageData<T> {
|
||||
total: number;
|
||||
list: T[];
|
||||
}
|
||||
|
||||
/** API 统一响应格式 */
|
||||
export interface ApiResult<T> {
|
||||
code: number | string;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
@@ -101,4 +101,6 @@ export interface WithdrawInfoVO {
|
||||
auditImg: string;
|
||||
/** 审核图片列表 */
|
||||
auditImgList: string[];
|
||||
/** 审核结果 */
|
||||
checkMsg: string;
|
||||
}
|
||||
|
||||
@@ -165,9 +165,9 @@ const PageTabs = defineComponent({
|
||||
case 'other':
|
||||
props.onCloseOther?.(props.activeKey);
|
||||
break;
|
||||
case 'all':
|
||||
props.onCloseAll?.();
|
||||
break;
|
||||
// case 'all':
|
||||
// props.onCloseAll?.();
|
||||
// break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -278,7 +278,7 @@ const PageTabs = defineComponent({
|
||||
{renderBatchMenuItem('left', renderAiIcon(LuPanelLeftClose), '关闭左侧标签页')}
|
||||
{renderBatchMenuItem('right', renderAiIcon(LuPanelRightClose), '关闭右侧标签页')}
|
||||
{renderBatchMenuItem('other', renderAiIcon(LuCopyX), '关闭其它标签页')}
|
||||
{renderBatchMenuItem('all', renderAiIcon(LuListX), '关闭全部标签页')}
|
||||
{/* {renderBatchMenuItem('all', renderAiIcon(LuListX), '关闭全部标签页')} */}
|
||||
</Menu>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -164,6 +164,15 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
|
||||
icon: 'AiOutlineSafetyCertificate',
|
||||
permission: 'system.roles.view',
|
||||
},
|
||||
{
|
||||
id: 'system_params',
|
||||
name: '参数配置',
|
||||
path: 'system/params',
|
||||
component: 'system/params',
|
||||
componentName: 'SystemParams',
|
||||
icon: 'AiOutlineSetting',
|
||||
permission: 'system.params.view',
|
||||
},
|
||||
{
|
||||
id: 'system_logs',
|
||||
name: '操作日志',
|
||||
|
||||
@@ -296,7 +296,7 @@ export default defineComponent({
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
<Footer style={footerStyle}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</Footer>
|
||||
{/* <Footer style={footerStyle}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</Footer> */}
|
||||
</Layout>
|
||||
|
||||
{/* 修改密码弹窗 */}
|
||||
|
||||
@@ -28,12 +28,29 @@
|
||||
}
|
||||
|
||||
.desc {
|
||||
:global(.ant-descriptions-view table) {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.ant-descriptions-item-label) {
|
||||
width: 110px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
// 首列内容区加宽(省市区、公开活动、创建时间等)
|
||||
.wideCol:global(.ant-descriptions-item-content) {
|
||||
width: 38%;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
// 较长 label 加宽且不换行(class 同时挂在 label/content 上,需用复合选择器)
|
||||
.wideLabel:global(.ant-descriptions-item-label) {
|
||||
width: 132px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Item 的 class 会同时挂在 label/content 两个同级 td/th 上,不能用后代选择器
|
||||
.statCell:global(.ant-descriptions-item-content) {
|
||||
min-width: 60px;
|
||||
|
||||
@@ -81,6 +81,13 @@ const ID_CARD_IMG_COLUMN: any = {
|
||||
record ? renderIdImages(record.idCardImgList || [], record.idCardImg || '') : <span>--</span>,
|
||||
};
|
||||
|
||||
type ConditionalColumnKey = 'isRequireIdCardNo' | 'isRequireIdCardImg';
|
||||
|
||||
const CONDITIONAL_PLAYER_COLUMNS: { key: ConditionalColumnKey; column: any }[] = [
|
||||
{ key: 'isRequireIdCardNo', column: ID_CARD_COLUMN },
|
||||
{ key: 'isRequireIdCardImg', column: ID_CARD_IMG_COLUMN },
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EventDetailModal',
|
||||
props: {
|
||||
@@ -94,12 +101,12 @@ export default defineComponent({
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
|
||||
/** 动态列:根据赛事是否要求证件号/证件图片决定显示 */
|
||||
const playerColumns = computed(() => {
|
||||
const cols = [...BASE_PLAYER_COLUMNS];
|
||||
if (detail.value?.isRequireIdCardNo === '1') cols.push(ID_CARD_COLUMN);
|
||||
if (detail.value?.isRequireIdCardImg === '1') cols.push(ID_CARD_IMG_COLUMN);
|
||||
return cols;
|
||||
});
|
||||
const playerColumns = computed(() => [
|
||||
...BASE_PLAYER_COLUMNS,
|
||||
...CONDITIONAL_PLAYER_COLUMNS.filter(({ key }) => detail.value?.[key] === '1').map(
|
||||
({ column }) => column,
|
||||
),
|
||||
]);
|
||||
|
||||
// ===== 每个组别的选手数据(key = categoryId) =====
|
||||
const categoryPlayers = ref<Record<string, TournamentAdminInfoSignupVO[]>>({});
|
||||
@@ -181,7 +188,7 @@ export default defineComponent({
|
||||
<Modal
|
||||
visible={props.visible}
|
||||
title="查看详情"
|
||||
width={900}
|
||||
width={980}
|
||||
onCancel={props.onClose}
|
||||
footer={null}
|
||||
centered
|
||||
@@ -198,26 +205,32 @@ export default defineComponent({
|
||||
<div class={styles.section}>
|
||||
<div class={styles.sectionTitle}>基础信息</div>
|
||||
<Descriptions size="small" bordered column={3} class={styles.desc}>
|
||||
<Descriptions.Item label="赛事名称">{detail.value.name}</Descriptions.Item>
|
||||
<Descriptions.Item label="赛事时间" span={2}>
|
||||
<Descriptions.Item label="赛事名称" span={2}>
|
||||
{detail.value.name}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="赛事时间">
|
||||
{detail.value.startTimeBegin} 至 {detail.value.startTimeEnd}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="赛事说明" span={3}>
|
||||
{detail.value.descr}
|
||||
<div style={{ whiteSpace: 'pre-wrap', maxHeight: '280px', overflowY: 'auto' }}>
|
||||
{detail.value.descr}
|
||||
</div>
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="报名起止时间" span={3}>
|
||||
<Descriptions.Item label="报名起止时间" span={3} class={styles.wideLabel}>
|
||||
{signupTimeDisplay(detail.value.signupTimeBegin, detail.value.signupTimeEnd)}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="省市区">{detail.value.venueArea}</Descriptions.Item>
|
||||
<Descriptions.Item label="省市区" class={styles.wideCol}>
|
||||
{detail.value.venueArea}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="地点">{detail.value.venue}</Descriptions.Item>
|
||||
<Descriptions.Item label="场地编号" class={styles.statCell}>
|
||||
{detail.value.venueNo || '--'}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="公开活动">
|
||||
<Descriptions.Item label="公开活动" class={styles.wideCol}>
|
||||
{formatPublic(detail.value.isPublic)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="证件号">
|
||||
@@ -227,12 +240,16 @@ export default defineComponent({
|
||||
{formatYN(detail.value.isRequireIdCardImg)}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="创建时间">{detail.value.createTime}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间" class={styles.wideCol}>
|
||||
{detail.value.createTime}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建人" span={2}>
|
||||
{detail.value.createNickname}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="报名人数">{detail.value.signupCount}</Descriptions.Item>
|
||||
<Descriptions.Item label="报名人数" class={styles.wideCol}>
|
||||
{detail.value.signupCount}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="退赛人数">{detail.value.dropoutCount}</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="最大人数"
|
||||
@@ -241,7 +258,7 @@ export default defineComponent({
|
||||
{detail.value.totalLimit || '--'}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="总对阵数">
|
||||
<Descriptions.Item label="总对阵数" class={styles.wideCol}>
|
||||
{detail.value.matchTotalCount}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="已完成对阵数" span={2}>
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f0f0f0;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.imageGrid {
|
||||
|
||||
@@ -89,7 +89,7 @@ export default defineComponent({
|
||||
const renderAction = (record: any) => {
|
||||
const status = record.status;
|
||||
|
||||
if (status === '0' || status === '1') {
|
||||
if (['0', '1', '2'].includes(status)) {
|
||||
return (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => openDetailModal(record)}>
|
||||
|
||||
@@ -58,11 +58,7 @@ const PLAYER_COLUMNS = [
|
||||
key: 'idCardImg',
|
||||
width: 170,
|
||||
customRender: ({ record }: { record: TournamentAdminOrderInfoPageVO }) => {
|
||||
const urls = record.idCardImgList?.length
|
||||
? record.idCardImgList
|
||||
: record.idCardImg
|
||||
? [record.idCardImg]
|
||||
: [];
|
||||
const urls = record.idCardImgList || [];
|
||||
if (!urls.length) return <span>--</span>;
|
||||
return (
|
||||
<Space>
|
||||
|
||||
@@ -180,7 +180,7 @@ export default defineComponent({
|
||||
},
|
||||
{
|
||||
key: 'totalBalance',
|
||||
label: '平台余额(所有用户余额总和)',
|
||||
label: '平台余额(所有用户余额总和 = 总收入 - 提现金额)',
|
||||
value: s.totalBalance,
|
||||
bgColor: '#e6f4ff',
|
||||
accentColor: '#1677ff',
|
||||
@@ -224,7 +224,11 @@ export default defineComponent({
|
||||
<div class={styles.statValue} style={{ color: item.accentColor }}>
|
||||
¥{formatMoney(item.value)}
|
||||
</div>
|
||||
{/* 趋势文字暂时隐藏 */}
|
||||
{/* {item.key === 'totalBalance' && (
|
||||
<div class={styles.statDesc} style={{ color: 'rgba(0, 0, 0, 0.45)' }}>
|
||||
= 总收入 - 提现金额
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,9 @@ export function useReportsModel() {
|
||||
// ===== 图表数据 =====
|
||||
const chartData = computed(() => {
|
||||
const list = data.value?.monthList || [];
|
||||
return list.map((item) => ({
|
||||
// 按月份升序排列,确保图表 X 轴时间顺序正确
|
||||
const sorted = [...list].sort((a, b) => a.month.localeCompare(b.month));
|
||||
return sorted.map((item) => ({
|
||||
month: dayjs(item.month).format('MM月'),
|
||||
order: toNumber(item.orderAmount),
|
||||
refund: toNumber(item.refundAmount),
|
||||
|
||||
@@ -96,16 +96,12 @@
|
||||
min-width: 0;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.descValueBold {
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.auditImgList {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
|
||||
@@ -150,17 +150,17 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = (label: string, value: any, isBold = false) => (
|
||||
const renderItem = (label: string, value: any) => (
|
||||
<div class={styles.descItem}>
|
||||
<span class={styles.descLabel}>{label}</span>
|
||||
<span class={[styles.descValue, isBold ? styles.descValueBold : '']}>{value || '--'}</span>
|
||||
<span class={styles.descValue}>{value || '--'}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
/** 渲染内容区(加载完成后) */
|
||||
const renderContent = () => {
|
||||
const d = detail.value!;
|
||||
const bankCardTypeLabel = d.bankCardType === 1 ? '个人' : d.bankCardType || '--';
|
||||
const bankCardTypeLabel = d.bankCardType == 1 ? '个人' : d.bankCardType || '--';
|
||||
const isPending = d.auditStatus == 0;
|
||||
|
||||
return (
|
||||
@@ -175,12 +175,12 @@ export default defineComponent({
|
||||
{renderItem('申请时间:', d.createDate)}
|
||||
{renderItem('打款类型:', d.withdrawType)}
|
||||
<div style="display: contents;" />
|
||||
{renderItem('提现金额:', `¥${d.withdrawAmount}`, true)}
|
||||
{renderItem('提现金额:', `¥${d.withdrawAmount}`)}
|
||||
{renderItem('费率:', `${(Number(d.feeRate) * 100).toFixed(2)}%`)}
|
||||
{renderItem('手续费:', `¥${d.feeAmount}`)}
|
||||
{renderItem('到账金额:', `¥${d.receivedAmount}`, true)}
|
||||
{renderItem('审核状态:', getAuditStatusText(d.auditStatus), true)}
|
||||
{renderItem('列账状态:', getPayStatusText(d.payStatus))}
|
||||
{renderItem('到账金额:', `¥${d.receivedAmount}`)}
|
||||
{renderItem('审核状态:', getAuditStatusText(d.auditStatus))}
|
||||
{renderItem('到账状态:', getPayStatusText(d.payStatus))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -267,6 +267,7 @@ export default defineComponent({
|
||||
<div class={styles.infoColumn}>
|
||||
{renderItem('审核时间:', d.auditDate)}
|
||||
{renderItem('审核人:', d.auditNickname)}
|
||||
{d.checkMsg && renderItem('审核内容:', d.checkMsg)}
|
||||
<div class={styles.descItem}>
|
||||
<span class={styles.descLabel}>审核图片:</span>
|
||||
{d.auditImgList?.length ? (
|
||||
|
||||
@@ -130,7 +130,7 @@ export default defineComponent({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={styles.brandFooter}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</div>
|
||||
{/* <div class={styles.brandFooter}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -186,7 +186,7 @@ export default defineComponent({
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<div class={styles.loginFooter}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</div>
|
||||
{/* <div class={styles.loginFooter}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ export const ACTION_TYPE_OPTIONS = [
|
||||
{ value: '2', label: '上架赛事' },
|
||||
{ value: '3', label: '禁用用户' },
|
||||
{ value: '4', label: '启用用户' },
|
||||
{ value: '7', label: '系统参数' },
|
||||
] as const;
|
||||
|
||||
/** 操作类型文案映射 */
|
||||
@@ -31,6 +32,7 @@ export const ACTION_TYPE_MAP: Record<number, string> = {
|
||||
2: '上架赛事',
|
||||
3: '禁用用户',
|
||||
4: '启用用户',
|
||||
7: '系统参数',
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.paramFormModalMain {
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { defineComponent, ref, reactive, nextTick } from 'vue';
|
||||
import { useEffect } from '@/hooks';
|
||||
import { Modal, Form, Input, Button } from 'ant-design-vue';
|
||||
import { paramCodeRules, paramValueRules, remarkRules } from '../model/form';
|
||||
import type { SysParamsDTO } from '../model/services';
|
||||
import styles from './ParamFormModal.module.less';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface ParamFormModalProps {
|
||||
visible: boolean;
|
||||
record: SysParamsDTO | null;
|
||||
submitting?: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (formData: { paramCode: string; paramValue: string; remark: string }) => void;
|
||||
}
|
||||
|
||||
/** 默认表单数据 */
|
||||
const getDefaultForm = () => ({
|
||||
paramCode: '',
|
||||
paramValue: '',
|
||||
remark: '',
|
||||
});
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ParamFormModal',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
record: { type: Object, default: null },
|
||||
submitting: { type: Boolean, default: false },
|
||||
onClose: { type: Function, required: true },
|
||||
onSave: { type: Function, required: true },
|
||||
},
|
||||
setup(props: ParamFormModalProps) {
|
||||
const formRef = ref<any>();
|
||||
const formData = reactive(getDefaultForm());
|
||||
|
||||
/** 根据 record 初始化表单 */
|
||||
const initFormFromRecord = (record: SysParamsDTO | null) => {
|
||||
if (record) {
|
||||
Object.assign(formData, {
|
||||
paramCode: record.paramCode || '',
|
||||
paramValue: record.paramValue || '',
|
||||
remark: record.remark || '',
|
||||
});
|
||||
} else {
|
||||
Object.assign(formData, getDefaultForm());
|
||||
}
|
||||
};
|
||||
|
||||
/** 监听 visible 变化重置表单 */
|
||||
useEffect(() => {
|
||||
if (props.visible) {
|
||||
initFormFromRecord(props.record);
|
||||
nextTick().then(() => formRef.value?.clearValidate());
|
||||
}
|
||||
}, [() => props.visible]);
|
||||
|
||||
/** 提交 */
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
|
||||
props.onSave({
|
||||
paramCode: formData.paramCode,
|
||||
paramValue: formData.paramValue,
|
||||
remark: formData.remark,
|
||||
});
|
||||
} catch {
|
||||
// 校验未通过,Form 已展示错误,无需处理
|
||||
}
|
||||
};
|
||||
|
||||
/** 关闭 */
|
||||
const handleClose = () => {
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
return () => {
|
||||
const isEdit = !!props.record;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? '编辑配置' : '新增配置'}
|
||||
visible={props.visible}
|
||||
onCancel={handleClose}
|
||||
width={520}
|
||||
destroyOnClose
|
||||
footer={null}
|
||||
centered
|
||||
wrapClassName={styles.paramFormModalMain}
|
||||
>
|
||||
<Form ref={formRef} layout="vertical" model={formData} requiredMark>
|
||||
<Form.Item label="参数编码" name="paramCode" rules={paramCodeRules}>
|
||||
<Input
|
||||
value={formData.paramCode}
|
||||
onUpdate:value={(val: any) => {
|
||||
formData.paramCode = val?.target?.value ?? val ?? '';
|
||||
}}
|
||||
placeholder="请输入参数编码"
|
||||
allowClear
|
||||
readonly={isEdit}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="参数值" name="paramValue" rules={paramValueRules}>
|
||||
<Input
|
||||
value={formData.paramValue}
|
||||
onUpdate:value={(val: any) => {
|
||||
formData.paramValue = val?.target?.value ?? val ?? '';
|
||||
}}
|
||||
placeholder="请输入参数值"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="备注说明" name="remark" rules={remarkRules}>
|
||||
<TextArea
|
||||
value={formData.remark}
|
||||
onUpdate:value={(val: any) => {
|
||||
formData.remark = val?.target?.value ?? val ?? '';
|
||||
}}
|
||||
placeholder="请输入备注说明(选填,最多200字)"
|
||||
maxlength={200}
|
||||
showCount
|
||||
rows={3}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{/* 底部按钮 */}
|
||||
<div class={styles.footer}>
|
||||
<Button onClick={handleClose}>取消</Button>
|
||||
<Button type="primary" loading={props.submitting} onClick={handleSubmit}>
|
||||
确认保存
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
.cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
}
|
||||
|
||||
.view {
|
||||
flex-shrink: 0;
|
||||
color: #1677ff;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import { defineComponent, ref, nextTick } from 'vue';
|
||||
import { Button, Input, Table, Form, Space, Modal, Pagination, message } from 'ant-design-vue';
|
||||
import { useParamsModel } from './model/useParamsModel';
|
||||
import { saveParam, updateParam } from './model/services';
|
||||
import { useState, useThrottleFn, useContainerSize, useEffect } from '@/hooks';
|
||||
import ParamFormModal from './components/ParamFormModal';
|
||||
import pageStyles from '@/assets/styles/pageLayout.module.less';
|
||||
import cellStyles from './index.module.less';
|
||||
|
||||
/**
|
||||
* 参数值单元格:最多 2 行截断,溢出时显示"查看"按钮
|
||||
*/
|
||||
const ParamValueCell = defineComponent({
|
||||
name: 'ParamValueCell',
|
||||
props: { text: { type: String, required: true } },
|
||||
setup(props) {
|
||||
const textRef = ref<HTMLElement | null>(null);
|
||||
const [overflow, setOverflow] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const checkOverflow = () => {
|
||||
const el = textRef.value;
|
||||
if (el) setOverflow(el.scrollHeight > el.clientHeight);
|
||||
};
|
||||
|
||||
let observer: ResizeObserver | null = null;
|
||||
useEffect(() => {
|
||||
if (!textRef.value) return;
|
||||
nextTick(checkOverflow);
|
||||
observer = new ResizeObserver(checkOverflow);
|
||||
observer.observe(textRef.value);
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
observer = null;
|
||||
};
|
||||
}, [() => textRef.value]);
|
||||
|
||||
return () => {
|
||||
const raw = props.text;
|
||||
return (
|
||||
<div class={cellStyles.cell}>
|
||||
<div ref={textRef} class={cellStyles.text}>
|
||||
{raw}
|
||||
</div>
|
||||
{overflow.value ? (
|
||||
<span class={cellStyles.view} onClick={() => setModalOpen(true)}>
|
||||
查看
|
||||
</span>
|
||||
) : null}
|
||||
<Modal
|
||||
visible={modalOpen.value}
|
||||
title="参数值"
|
||||
footer={null}
|
||||
width={600}
|
||||
centered
|
||||
destroyOnClose
|
||||
onCancel={() => setModalOpen(false)}
|
||||
>
|
||||
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: 1.8 }}>
|
||||
{raw}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* bodyCell 渲染
|
||||
*/
|
||||
function renderBodyCell({
|
||||
column,
|
||||
text,
|
||||
record,
|
||||
onEdit,
|
||||
}: {
|
||||
column: any;
|
||||
text: any;
|
||||
record: any;
|
||||
onEdit: (record: any) => void;
|
||||
}) {
|
||||
// 操作列
|
||||
if (column.key === 'action') {
|
||||
return (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => onEdit(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
// 参数值列:两行截断 + 查看弹窗
|
||||
if (column.dataIndex === 'paramValue') {
|
||||
const raw = text || '';
|
||||
return raw ? <ParamValueCell text={raw} /> : <span>--</span>;
|
||||
}
|
||||
// 备注列:空值显示 --
|
||||
if (column.dataIndex === 'remark' && !text) {
|
||||
return <span>--</span>;
|
||||
}
|
||||
return <span>{text ?? '--'}</span>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统管理 - 配置管理
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'SystemParams',
|
||||
setup() {
|
||||
const {
|
||||
filterForm,
|
||||
loading,
|
||||
dataSource,
|
||||
columns,
|
||||
pagination,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
} = useParamsModel();
|
||||
|
||||
const { containerRef, height } = useContainerSize({ headerOffset: 55 });
|
||||
|
||||
// ===== 弹窗状态 =====
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
const [editingRecord, setEditingRecord] = useState<any>(null);
|
||||
const [formSubmitting, setFormSubmitting] = useState<boolean>(false);
|
||||
|
||||
// ===== 新增 =====
|
||||
const handleAdd = () => {
|
||||
setEditingRecord(null);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// ===== 编辑 =====
|
||||
const handleEdit = (record: any) => {
|
||||
setEditingRecord(record);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// ===== 关闭弹窗 =====
|
||||
const handleCloseModal = () => {
|
||||
setModalVisible(false);
|
||||
setEditingRecord(null);
|
||||
};
|
||||
|
||||
// ===== 保存(新增/编辑) =====
|
||||
const handleSave = useThrottleFn(async (formPayload: any) => {
|
||||
if (formSubmitting.value) return;
|
||||
setFormSubmitting(true);
|
||||
try {
|
||||
let res: any;
|
||||
if (editingRecord.value) {
|
||||
res = await updateParam({
|
||||
id: editingRecord.value.id,
|
||||
paramCode: formPayload.paramCode,
|
||||
paramValue: formPayload.paramValue,
|
||||
remark: formPayload.remark,
|
||||
});
|
||||
} else {
|
||||
res = await saveParam({
|
||||
paramCode: formPayload.paramCode,
|
||||
paramValue: formPayload.paramValue,
|
||||
remark: formPayload.remark,
|
||||
});
|
||||
}
|
||||
if (res.code == 200) {
|
||||
message.success(editingRecord.value ? '编辑成功' : '新增成功');
|
||||
handleCloseModal();
|
||||
handleSearch();
|
||||
} else {
|
||||
message.error(res.msg || '操作失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('保存配置失败:', error);
|
||||
} finally {
|
||||
setFormSubmitting(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// ===== 表格最终列(含操作列) =====
|
||||
const tableColumns = [
|
||||
...columns,
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
fixed: 'right' as const,
|
||||
align: 'center' as const,
|
||||
},
|
||||
];
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
<div class={pageStyles.filter}>
|
||||
<Form layout="inline" model={filterForm}>
|
||||
<Form.Item label="参数编码" name="paramCode">
|
||||
<Input
|
||||
value={filterForm.paramCode}
|
||||
placeholder="请输入参数编码"
|
||||
style={{ width: '200px' }}
|
||||
allowClear
|
||||
onUpdate:value={(val: any) =>
|
||||
(filterForm.paramCode = val?.target?.value ?? val ?? '')
|
||||
}
|
||||
onPressEnter={handleSearch}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button onClick={handleReset}>重置</Button>
|
||||
<Button type="primary" onClick={handleSearch} loading={loading.value}>
|
||||
查询
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleAdd}>
|
||||
新增
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
{/* ===== 表格区 ===== */}
|
||||
<div class={pageStyles.table}>
|
||||
<div ref={containerRef} class={pageStyles.tableBody}>
|
||||
<Table
|
||||
columns={tableColumns}
|
||||
dataSource={dataSource.value}
|
||||
loading={loading.value}
|
||||
scroll={{ x: 'max-content', y: height.value }}
|
||||
pagination={false}
|
||||
>
|
||||
{{
|
||||
bodyCell: (args: any) =>
|
||||
renderBodyCell({
|
||||
...args,
|
||||
onEdit: handleEdit,
|
||||
}),
|
||||
}}
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* 独立分页 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 新增/编辑弹窗 ===== */}
|
||||
{modalVisible.value && (
|
||||
<ParamFormModal
|
||||
visible={modalVisible.value}
|
||||
record={editingRecord.value}
|
||||
submitting={formSubmitting.value}
|
||||
onClose={handleCloseModal}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 参数配置 - 常量配置
|
||||
*
|
||||
* 集中放置该页面的下拉选项、字段映射、状态文案等常量
|
||||
*/
|
||||
import type { SelectOption } from '@/types';
|
||||
|
||||
// ============================================================
|
||||
// 字段映射(表格列与 API 字段对齐)
|
||||
// ============================================================
|
||||
|
||||
/** API 字段 → 表格列中文映射 */
|
||||
export const FIELD_MAPPINGS: Record<string, string> = {
|
||||
// TODO: 等后端接口确定后,根据实际字段补充
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 下拉选项
|
||||
// ============================================================
|
||||
|
||||
/** 示例:状态筛选选项 */
|
||||
export const STATUS_OPTIONS: SelectOption[] = [{ value: '', label: '全部' }];
|
||||
|
||||
/** 示例:每页条数选项 */
|
||||
export const PAGE_SIZE_OPTIONS: SelectOption[] = [
|
||||
{ value: 10, label: '10条/页' },
|
||||
{ value: 20, label: '20条/页' },
|
||||
{ value: 50, label: '50条/页' },
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 参数配置 - 表单校验规则
|
||||
*
|
||||
* 集中管理新增/编辑弹窗的表单校验规则。
|
||||
*/
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
// ============================================================
|
||||
// 校验规则
|
||||
// ============================================================
|
||||
|
||||
/** 参数编码:必填 */
|
||||
export const paramCodeRules: Rule[] = [{ required: true, message: '请输入参数编码' }];
|
||||
|
||||
/** 参数值:必填 */
|
||||
export const paramValueRules: Rule[] = [{ required: true, message: '请输入参数值' }];
|
||||
|
||||
/** 备注说明:选填,最多 200 字 */
|
||||
export const remarkRules: Rule[] = [{ max: 200, message: '备注说明最多 200 个字' }];
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 参数配置 - 接口服务
|
||||
*
|
||||
* 该页面使用的所有接口集中管理,供 useModel Hook 调用。
|
||||
*/
|
||||
import { get, post } from '@/utils/request';
|
||||
|
||||
// ============================================================
|
||||
// 类型定义
|
||||
// ============================================================
|
||||
|
||||
/** 配置项 VO */
|
||||
export interface SysParamsDTO {
|
||||
id: string;
|
||||
paramCode: string;
|
||||
paramValue: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
/** 分页查询参数 */
|
||||
export interface ParamQueryParams {
|
||||
page: string;
|
||||
limit: string;
|
||||
paramCode?: string;
|
||||
}
|
||||
|
||||
/** 新增参数 */
|
||||
export interface ParamSaveParams {
|
||||
paramCode: string;
|
||||
paramValue: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** 编辑参数 */
|
||||
export interface ParamUpdateParams {
|
||||
id: string;
|
||||
paramCode: string;
|
||||
paramValue: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** 分页数据结构 */
|
||||
export interface PageData<T> {
|
||||
total: number;
|
||||
list: T[];
|
||||
exData?: Record<string, any>;
|
||||
}
|
||||
|
||||
/** 接口通用返回结构 */
|
||||
export interface ApiResult<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// URL 常量
|
||||
// ============================================================
|
||||
|
||||
const PARAM_PAGE = '/sys/params/page';
|
||||
const PARAM_SAVE = '/sys/params/save';
|
||||
const PARAM_UPDATE = '/sys/params/update';
|
||||
|
||||
// ============================================================
|
||||
// API 函数
|
||||
// ============================================================
|
||||
|
||||
/** 配置列表分页查询 */
|
||||
export function getParamList(params: ParamQueryParams): Promise<ApiResult<PageData<SysParamsDTO>>> {
|
||||
return get(PARAM_PAGE, params as Record<string, any>);
|
||||
}
|
||||
|
||||
/** 新增配置 */
|
||||
export function saveParam(params: ParamSaveParams): Promise<ApiResult<null>> {
|
||||
return post(PARAM_SAVE, params);
|
||||
}
|
||||
|
||||
/** 编辑配置 */
|
||||
export function updateParam(params: ParamUpdateParams): Promise<ApiResult<null>> {
|
||||
return post(PARAM_UPDATE, params);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 参数配置 - 表格列配置(视图层)
|
||||
* 纯展示逻辑,不包含状态管理
|
||||
*/
|
||||
import type { SysParamsDTO } from './services';
|
||||
|
||||
/**
|
||||
* 参数配置表格列定义
|
||||
*
|
||||
* 列:参数编码 / 参数值 / 备注说明
|
||||
* 操作列在 index.tsx 中动态追加
|
||||
*/
|
||||
export function useParamsColumns() {
|
||||
const columns = [
|
||||
{
|
||||
title: '参数编码',
|
||||
dataIndex: 'paramCode',
|
||||
key: 'paramCode',
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
title: '参数值',
|
||||
dataIndex: 'paramValue',
|
||||
key: 'paramValue',
|
||||
width: 480,
|
||||
},
|
||||
{
|
||||
title: '备注说明',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
minWidth: 240,
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { computed, reactive, toRef, nextTick, type Ref } from 'vue';
|
||||
import { useThrottleFn, useDebounce } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination, syncPaginationTotal } from '@/hooks/usePagination';
|
||||
import {
|
||||
getParamList,
|
||||
type SysParamsDTO,
|
||||
type ParamQueryParams,
|
||||
type PageData,
|
||||
type ApiResult,
|
||||
} from './services';
|
||||
import { useParamsColumns } from './useParamsColumns';
|
||||
|
||||
// ============================================================
|
||||
// Model(业务逻辑 Hook)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 参数配置页数据模型
|
||||
*/
|
||||
export function useParamsModel() {
|
||||
// ===== 筛选条件 =====
|
||||
const filterForm = reactive({
|
||||
paramCode: '',
|
||||
});
|
||||
|
||||
// 参数编码筛选防抖 300ms
|
||||
const { debouncedValue: debouncedParamCode } = useDebounce(
|
||||
toRef(filterForm, 'paramCode') as Ref<string>,
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): ParamQueryParams => {
|
||||
const params: ParamQueryParams = {
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
if (filterForm.paramCode.trim()) {
|
||||
params.paramCode = filterForm.paramCode.trim();
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 数据请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<ApiResult<PageData<SysParamsDTO>>>(() => getParamList(buildQueryParams()), {
|
||||
refreshDeps: [],
|
||||
formatResult: (res) => res,
|
||||
});
|
||||
|
||||
const dataSource = computed(() => {
|
||||
const res = data.value;
|
||||
return res ? (res as any).data?.list || [] : [];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
syncPaginationTotal(pagination, (data.value as any)?.data?.total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 表格列配置 =====
|
||||
const { columns } = useParamsColumns();
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => {
|
||||
return debouncedParamCode.value.trim() !== '';
|
||||
});
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.reset();
|
||||
fetchList();
|
||||
};
|
||||
|
||||
/** 重置(重置后自动查询) */
|
||||
const handleReset = useThrottleFn(async () => {
|
||||
filterForm.paramCode = '';
|
||||
pagination.reset();
|
||||
await fetchList();
|
||||
}, 500);
|
||||
|
||||
/** 分页变更 */
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
nextTick().then(() => fetchList());
|
||||
};
|
||||
|
||||
return {
|
||||
filterForm,
|
||||
loading,
|
||||
dataSource,
|
||||
columns,
|
||||
pagination,
|
||||
hasFilter,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
fetchList,
|
||||
};
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
declare module 'china-area-data' {
|
||||
declare module 'china-area-data/data.json' {
|
||||
const data: Record<string, Record<string, string>>;
|
||||
export default data;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import areaData from 'china-area-data';
|
||||
import areaData from 'china-area-data/data.json';
|
||||
|
||||
export interface CascadeOption {
|
||||
value: string;
|
||||
|
||||
Reference in New Issue
Block a user