feat(merchant): 添加平台费用字段并优化费率表编辑功能

- 在API模型和类型定义中新增platformFee字段
- 更新文档自动生成文件以包含新字段
- 重构费率表格组件,添加JSON批量编辑功能
- 实现表格与JSON数据的实时双向同步
- 添加还原默认数据按钮和相关逻辑
- 引入防抖函数优化JSON输入响应性能
- 添加数据验证确保输入格式正确性
- 更新表格列配置以显示商户加点字段
- 改进UI样式增加序列化编辑区域
- 添加新的lint脚本到本地设置配置
This commit is contained in:
danial
2025-11-02 17:43:15 +08:00
parent 50e147087a
commit 44b6e97b4d
5 changed files with 202 additions and 66 deletions

View File

@@ -5,7 +5,8 @@
"mcp__mysql__execute",
"Bash(pnpm type:check)",
"Bash(npx eslint:*)",
"mcp__ide__getDiagnostics"
"mcp__ide__getDiagnostics",
"Bash(pnpm lint:*)"
],
"deny": [],
"ask": []

View File

@@ -7,6 +7,7 @@
| **factLabel** | **number** | 实际面值 | [optional] [default to undefined] |
| **showLabel** | **number** | 展示面额 | [optional] [default to undefined] |
| **platformLabel** | **string** | 平台 | [optional] [default to undefined] |
| **platformFee** | **number** | 平台费 | [optional] [default to undefined] |
| **isLinkSingle** | **boolean** | 链接是否单独放置 | [optional] [default to undefined] |
| **value** | **number** | 费率 | [optional] [default to undefined] |
| **linkID** | **string** | 链接 | [optional] [default to undefined] |
@@ -21,6 +22,7 @@ const instance: KamiApiMerchantV1PlatformRateRecord = {
factLabel,
showLabel,
platformLabel,
platformFee,
isLinkSingle,
value,
linkID,

View File

@@ -25,6 +25,10 @@ export interface KamiApiMerchantV1PlatformRateRecord {
* 平台
*/
platformLabel?: string;
/**
* 平台费
*/
platformFee?: number;
/**
* 链接是否单独放置
*/

View File

@@ -9,6 +9,7 @@ export type merchantDeployRate = {
isLinkSingle: boolean;
value: number;
linkID: string;
platformFee: number;
sort?: number;
};

View File

@@ -1,70 +1,104 @@
<template>
<a-table
:data="tableData"
:columns="columns"
class="rate-table"
:pagination="false"
column-resizable
:bordered="{ cell: true }"
>
<template #sort="{ rowIndex }">
<a-input-number v-model="tableData[rowIndex].sort" required />
</template>
<template #factLabel="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].factLabel"
:precision="2"
required
/>
</template>
<template #showLabel="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].showLabel"
:precision="2"
required
/>
</template>
<template #value="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].value"
:precision="2"
required
/>
</template>
<template #platformLabel="{ rowIndex }">
<a-input v-model="tableData[rowIndex].platformLabel" required />
</template>
<template #isLinkSingle="{ rowIndex }">
<a-select v-model="tableData[rowIndex].isLinkSingle" required>
<a-option :value="true" label="是" />
<a-option :value="false" label="否" />
</a-select>
</template>
<template #linkID="{ rowIndex }">
<a-input v-model="tableData[rowIndex].linkID" required />
</template>
<template #action="{ rowIndex }">
<a-space size="mini">
<a-button size="small" status="warning" @click="addRecord(rowIndex)">
<template #icon>
<icon-plus />
</template>
</a-button>
<a-tooltip content="复制链接">
<a-button size="small" status="success" @click="openLink(rowIndex)">
<div class="rate-table-container">
<div class="serialization-section">
<a-space direction="vertical" style="width: 100%; margin-bottom: 16px">
<a-typography-text type="secondary">
批量编辑 (JSON格式) - 与表格实时同步
</a-typography-text>
<a-textarea
v-model="serializationData"
placeholder="在此编辑JSON数据表格会实时同步更新"
auto-size
allow-clear
/>
<a-space>
<a-button size="small" status="warning" @click="resetToDefault">
<template #icon>
<icon-copy />
<icon-refresh />
</template>
还原默认
</a-button>
</a-space>
</a-space>
</div>
<a-table
:data="tableData"
:columns="columns"
class="rate-table"
:pagination="false"
column-resizable
:bordered="{ cell: true }"
>
<template #sort="{ rowIndex }">
<a-input-number v-model="tableData[rowIndex].sort" required />
</template>
<template #factLabel="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].factLabel"
:precision="2"
required
/>
</template>
<template #showLabel="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].showLabel"
:precision="2"
required
/>
</template>
<template #value="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].value"
:precision="2"
required
/>
</template>
<template #platformLabel="{ rowIndex }">
<a-input v-model="tableData[rowIndex].platformLabel" required />
</template>
<template #isLinkSingle="{ rowIndex }">
<a-select v-model="tableData[rowIndex].isLinkSingle" required>
<a-option :value="true" label="是" />
<a-option :value="false" label="否" />
</a-select>
</template>
<template #platformFee="{ rowIndex }">
<a-input-number
v-model="tableData[rowIndex].platformFee"
:precision="2"
required
/>
</template>
<template #linkID="{ rowIndex }">
<a-input v-model="tableData[rowIndex].linkID" required />
</template>
<template #action="{ rowIndex }">
<a-space size="mini">
<a-button size="small" status="warning" @click="addRecord(rowIndex)">
<template #icon>
<icon-plus />
</template>
</a-button>
</a-tooltip>
<a-button size="small" status="danger" @click="deleteRecord(rowIndex)">
<template #icon>
<icon-delete />
</template>
</a-button>
</a-space>
</template>
</a-table>
<a-tooltip content="复制链接">
<a-button size="small" status="success" @click="openLink(rowIndex)">
<template #icon>
<icon-copy />
</template>
</a-button>
</a-tooltip>
<a-button
size="small"
status="danger"
@click="deleteRecord(rowIndex)"
>
<template #icon>
<icon-delete />
</template>
</a-button>
</a-space>
</template>
</a-table>
</div>
</template>
<script lang="ts" setup>
@@ -73,7 +107,8 @@ import { openExternalLink } from './data';
import { useClipboard } from '@vueuse/core';
import { TableColumnData } from '@arco-design/web-vue/es/table';
import { Message } from '@arco-design/web-vue';
import { watch, watchEffect } from 'vue';
import { watchEffect, ref, watch } from 'vue';
import { useDebounceFn } from '@vueuse/core';
const generateTableData = () => {
return {
@@ -83,10 +118,12 @@ const generateTableData = () => {
isLinkSingle: true,
value: 0,
sort: 0,
platformFee: 0,
linkID: ''
};
};
const tableData = defineModel<merchantDeployRate[]>();
const serializationData = ref('');
const { copy } = useClipboard();
@@ -121,6 +158,11 @@ const columns: TableColumnData[] = [
dataIndex: 'value',
slotName: 'value'
},
{
title: '商户加点',
dataIndex: 'platformFee',
slotName: 'platformFee'
},
{
title: '商品链接',
dataIndex: 'linkID',
@@ -143,6 +185,62 @@ watchEffect(() => {
}
});
// 实时同步tableData 变化时更新 serializationData
watch(
tableData,
newData => {
try {
serializationData.value = JSON.stringify(newData, null, 2);
} catch (error) {
console.error('序列化失败:', error);
}
},
{ deep: true }
);
// 实时同步serializationData 变化时更新 tableData
const updateTableFromSerialization = useDebounceFn((newData: string) => {
try {
if (!newData?.trim()) {
return;
}
const parsedData = JSON.parse(newData);
if (!Array.isArray(parsedData)) {
throw new Error('数据格式必须是数组');
}
// 验证每个记录的字段
const validData = parsedData.map((item, index) => {
if (typeof item !== 'object' || item === null) {
throw new Error(`${index + 1}条记录格式不正确`);
}
return {
factLabel: Number(item.factLabel) || 0,
showLabel: Number(item.showLabel) || 0,
platformLabel: String(item.platformLabel || ''),
isLinkSingle: Boolean(item.isLinkSingle ?? true),
value: Number(item.value) || 0,
sort: Number(item.sort) || 0,
platformFee: Number(item.platformFee) || 0,
linkID: String(item.linkID || '')
};
});
// 避免循环更新,检查数据是否真的发生了变化
if (JSON.stringify(validData) !== JSON.stringify(tableData.value)) {
tableData.value = validData;
}
} catch (error) {
// JSON解析失败时静默处理不影响用户输入
console.error('反序列化失败:', error);
}
}, 300);
watch(() => serializationData.value, updateTableFromSerialization);
const openLink = async (rowIndex: number) => {
const { platformLabel, isLinkSingle, linkID } = tableData.value[rowIndex];
const link = openExternalLink(platformLabel, isLinkSingle, linkID);
@@ -153,6 +251,36 @@ const openLink = async (rowIndex: number) => {
const deleteRecord = (rowIndex: number) => {
tableData.value.splice(rowIndex, 1);
};
// 还原默认功能
const resetToDefault = () => {
const defaultData = [
{
factLabel: 95,
showLabel: 100,
platformLabel: '淘宝',
isLinkSingle: true,
value: 0.95,
sort: 1,
platformFee: 0,
linkID: ''
}
];
tableData.value = defaultData;
serializationData.value = '';
Message.success('已还原为默认数据');
};
</script>
<style lang="less"></style>
<style lang="less">
.rate-table-container {
.serialization-section {
background: #f7f8fa;
padding: 16px;
border-radius: 6px;
border: 1px solid #e5e6eb;
margin-bottom: 16px;
}
}
</style>