feat:
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled

1. 创建就诊人功能增强
2. 优化交互细节
This commit is contained in:
李琦
2026-08-10 15:33:08 +08:00
parent 871982fc07
commit b1f223554b
22 changed files with 1442 additions and 143 deletions

View File

@@ -170,6 +170,12 @@ export async function getFormulasByDrugName(drugName: string) {
has_unmatched: number;
status: number;
drug_count: number;
/** 命中味克数(拆分后每味继承) */
match_dose: string;
match_unit: string;
match_ancient_dose: string;
/** 展示用:如 9g(三两) */
match_dose_text: string;
}[]
>(`${missingPrefix}formulas-by-drug-name`, {
params: { drug_name: drugName },
@@ -177,20 +183,24 @@ export async function getFormulasByDrugName(drugName: string) {
}
/**
* 替换为药材表已有药材:金方 name 改为目标药名,旧名保留 origin_name 并挂别名
* 替换为药材表已有药材(支持 1→N 拆分)
* xk-api POST /golden-formula-missing-drug/replace-with-drug
*/
export async function replaceMissingWithDrug(data: {
id?: number;
drug_name?: string;
target_drug_id: number;
/** 预览勾选的金方 ID 列表(必填) */
formula_ids: number[];
/** 目标药材列表;多味时把错误合并名拆成多行 */
targets: Array<{ target_drug_id: number; dose?: string }>;
/** 兼容旧单目标(可选) */
target_drug_id?: number;
}) {
return requestClient.post<{
drug_id: number;
old_name: string;
new_name: string;
new_names: string[];
formula_count: number;
refreshed: number;
}>(`${missingPrefix}replace-with-drug`, data);

View File

@@ -3,7 +3,7 @@
* 缺失药处理面板
* - 添加药材:打开中药管理「新增中药」模态框
* - 挂别名:ChineseDrugNameBubble 选目标药
* - 替换已有:选目标药 → 预览勾选含同名药的金方 → 确认
* - 替换已有:可 1→N(错误合并名拆多味)→ 预览勾选金方 → 确认
*/
import { computed, ref, watch } from 'vue';
@@ -25,6 +25,13 @@ import {
resolveMissingByName,
} from '../api';
/** 替换目标行:只选药名,克数各方继承原味(不在此填写) */
interface ReplaceTargetRow {
key: string;
displayName: string;
drugId: number;
}
const props = defineProps<{
open: boolean;
missingId?: number;
@@ -46,26 +53,71 @@ const submitting = ref(false);
const selectedDrug = ref<ChineseDrugBubbleItem | null>(null);
const selectedDisplayName = ref('');
/** 替换目标列表(可多味) */
const replaceTargets = ref<ReplaceTargetRow[]>([]);
/** 当前正在搜索选择的那一行 */
const activeReplaceKey = ref('');
/** 替换预览:含该药名的金方 */
const previewLoading = ref(false);
const previewFormulas = ref<any[]>([]);
const selectedFormulaIds = ref<number[]>([]);
const previewColumns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 70 },
{ title: '药方名字', dataIndex: 'name', key: 'name', ellipsis: true },
{ title: '条文', dataIndex: 'clause_number', key: 'clause_number', width: 90 },
{ title: '来源', dataIndex: 'source_name', key: 'source_name', width: 100 },
{ title: '药材速览', dataIndex: 'herb_overview', key: 'herb_overview', ellipsis: true },
{ title: '标记', key: 'flags', width: 120 },
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
{ title: '药方名字', dataIndex: 'name', key: 'name', width: 120, ellipsis: true },
{ title: '条文', dataIndex: 'clause_number', key: 'clause_number', width: 70 },
{ title: '原克数', dataIndex: 'match_dose_text', key: 'match_dose_text', width: 110 },
{ title: '拆分后预览', key: 'split_preview', ellipsis: true },
{ title: '标记', key: 'flags', width: 110 },
];
const selectedFormulaCount = computed(() => selectedFormulaIds.value.length);
const validReplaceTargets = computed(() =>
replaceTargets.value.filter((r) => r.drugId > 0 && r.displayName),
);
const replaceTargetNamesText = computed(() =>
validReplaceTargets.value.map((r) => r.displayName).join('、'),
);
/**
* 按该方原味克数生成拆分预览文案(各方克数可能不同,统一继承本方原味)
*/
function buildSplitPreview(record: {
match_dose_text?: string;
match_dose?: string;
match_unit?: string;
match_ancient_dose?: string;
}): string {
const names = validReplaceTargets.value.map((r) => r.displayName);
if (!names.length) return '请先选择目标药材';
const doseText =
String(record?.match_dose_text || '').trim() ||
(() => {
const d = String(record?.match_dose || '').trim();
const u = String(record?.match_unit || 'g').trim() || 'g';
const a = String(record?.match_ancient_dose || '').trim();
let t = d ? `${d}${u}` : '';
if (a) t = t ? `${t}(${a})` : a;
return t;
})();
return names
.map((n) => (doseText ? `${n} ${doseText}` : n))
.join('、');
}
const [ChinaMedicineModal, chinaMedicineModalApi] = useVbenModal({
connectedComponent: ChinaMedicineFormModal,
});
function newReplaceRow(): ReplaceTargetRow {
return {
key: `rt-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
displayName: '',
drugId: 0,
};
}
watch(
() => props.open,
(v) => {
@@ -73,6 +125,8 @@ watch(
step.value = 'choose';
selectedDrug.value = null;
selectedDisplayName.value = '';
replaceTargets.value = [newReplaceRow()];
activeReplaceKey.value = replaceTargets.value[0]?.key || '';
previewFormulas.value = [];
selectedFormulaIds.value = [];
}
@@ -145,6 +199,57 @@ function resolveTargetDrugId(): number {
return Number(item.drug_id || item.drug?.id || 0);
}
/**
* 替换行选中药材(必须带 key,避免多行时写错位)
* 克数不在此填写:确认后由后端按各方原味 dose 继承
*/
function onSelectReplaceTarget(key: string, item: ChineseDrugBubbleItem) {
activeReplaceKey.value = key;
const row = replaceTargets.value.find((r) => r.key === key);
if (!row) return;
const drugId = Number(item.drug_id || item.drug?.id || 0);
const displayName = String(
item?.drug?.drug_name || item?.drug_name || '',
).trim();
if (!drugId || !displayName) {
message.warning('未识别到有效药材');
return;
}
// 同一列表禁止重复选同一味
const dup = replaceTargets.value.some(
(r) => r.key !== key && r.drugId === drugId,
);
if (dup) {
message.warning(`「${displayName}」已在目标列表中`);
return;
}
row.drugId = drugId;
row.displayName = displayName;
}
function focusReplaceRow(key: string) {
activeReplaceKey.value = key;
}
function addReplaceRow() {
const row = newReplaceRow();
replaceTargets.value.push(row);
activeReplaceKey.value = row.key;
}
function removeReplaceRow(key: string) {
if (replaceTargets.value.length <= 1) {
// 至少保留一行空位
replaceTargets.value = [newReplaceRow()];
activeReplaceKey.value = replaceTargets.value[0]!.key;
return;
}
replaceTargets.value = replaceTargets.value.filter((r) => r.key !== key);
if (!replaceTargets.value.some((r) => r.key === activeReplaceKey.value)) {
activeReplaceKey.value = replaceTargets.value[0]?.key || '';
}
}
/**
* 加载含该缺失药名的金方预览,并默认勾选
* - 有 formulaId:默认只勾当前方(可再加选)
@@ -162,11 +267,10 @@ async function loadFormulaPreview() {
previewFormulas.value = Array.isArray(list) ? list : [];
const allIds = previewFormulas.value.map((r) => Number(r.id)).filter((id) => id > 0);
if (props.formulaId && props.formulaId > 0) {
// 当前方优先勾选;若列表里没有当前方(异常),仍尽量带上
selectedFormulaIds.value = allIds.includes(props.formulaId)
? [props.formulaId]
: allIds.length
? [allIds[0]]
? [allIds[0]!]
: [];
} else {
selectedFormulaIds.value = allIds;
@@ -185,6 +289,8 @@ async function goPick(next: 'alias' | 'replace') {
selectedDrug.value = null;
selectedDisplayName.value = '';
if (next === 'replace') {
replaceTargets.value = [newReplaceRow()];
activeReplaceKey.value = replaceTargets.value[0]!.key;
await loadFormulaPreview();
} else {
previewFormulas.value = [];
@@ -229,12 +335,12 @@ async function confirmAlias() {
}
/**
* 确认替换:只改预览勾选的金方
* 确认替换:支持多味拆分,只改预览勾选的金方
*/
async function confirmReplace() {
const targetDrugId = resolveTargetDrugId();
if (!targetDrugId) {
message.warning('请先搜索并选择目标药材');
const targets = validReplaceTargets.value;
if (!targets.length) {
message.warning('请至少选择一味目标药材');
return;
}
if (!selectedFormulaIds.value.length) {
@@ -243,14 +349,23 @@ async function confirmReplace() {
}
submitting.value = true;
try {
// 不传 dose:后端按各方原味克数继承到拆出的每一味
const res = await replaceMissingWithDrug({
...buildBasePayload(),
target_drug_id: targetDrugId,
formula_ids: selectedFormulaIds.value,
targets: targets.map((t) => ({
target_drug_id: t.drugId,
})),
});
message.success(
`已将「${res?.old_name || props.drugName}」替换为「${res?.new_name || selectedDisplayName.value}」(共 ${Number(res?.formula_count || 0)} 个金方,旧名已保留)`,
);
const names =
Array.isArray(res?.new_names) && res.new_names.length
? res.new_names.join('、')
: res?.new_name || replaceTargetNamesText.value;
const tip =
targets.length > 1
? `已将「${res?.old_name || props.drugName}」拆分为「${names}」(共 ${Number(res?.formula_count || 0)} 个金方)`
: `已将「${res?.old_name || props.drugName}」替换为「${names}」(共 ${Number(res?.formula_count || 0)} 个金方,旧名已保留)`;
message.success(tip);
emit('done');
close();
} catch (e: any) {
@@ -267,7 +382,7 @@ async function confirmReplace() {
:title="`处理缺失药:${drugName || ''}`"
:footer="null"
destroy-on-close
:width="step === 'replace' ? 860 : 520"
:width="step === 'replace' ? 960 : 520"
@cancel="close"
>
<div v-if="step === 'choose'" class="gf-resolve-choose">
@@ -282,7 +397,7 @@ async function confirmReplace() {
放入药材别名表(挂到已有药,不改金方药名)
</Button>
<Button block @click="goPick('replace')">
替换为已有药材(预览勾选金方后确认)
替换为已有药材(可拆成多味,预览勾选金方后确认)
</Button>
</Space>
</div>
@@ -313,23 +428,42 @@ async function confirmReplace() {
</div>
<div v-else class="gf-resolve-replace">
<p class="gf-resolve-tip">
1)搜索并选择目标药材;2)预览勾选要一起修改的金方;3)确认后把「{{ drugName }}」替换为目标药名(旧名写入
origin_name 并挂别名)。
1)添加目标药材(可多味);2)下方预览各方原克数,拆分后每味都继承该方原味克数(各方可能不同,无需手填);3)勾选金方后确认。
</p>
<div class="gf-resolve-search">
<span class="gf-resolve-search-label">搜索中药</span>
<ChineseDrugNameBubble
:display-name="selectedDisplayName"
:type="1"
:store-id="searchStoreId"
placeholder="输入药名搜索"
@select="onSelectDrug"
/>
<div class="gf-replace-targets">
<div
v-for="(row, idx) in replaceTargets"
:key="row.key"
class="gf-replace-row"
:class="{ 'is-active': activeReplaceKey === row.key }"
@click="focusReplaceRow(row.key)"
>
<span class="gf-replace-idx">{{ idx + 1 }}</span>
<div class="gf-replace-search" @mousedown="focusReplaceRow(row.key)">
<ChineseDrugNameBubble
:display-name="row.displayName"
:type="1"
:store-id="searchStoreId"
placeholder="搜索并选择目标药"
@select="(item) => onSelectReplaceTarget(row.key, item)"
/>
</div>
<Button
type="link"
danger
size="small"
@click.stop="removeReplaceRow(row.key)"
>
删除
</Button>
</div>
<Button type="dashed" block class="gf-replace-add" @click="addReplaceRow">
+ 再加一味目标药
</Button>
<p v-if="validReplaceTargets.length" class="gf-resolve-selected">
将拆分为:{{ replaceTargetNamesText }}(共 {{ validReplaceTargets.length }} 味,克数各方继承原味)
</p>
</div>
<p v-if="selectedDisplayName" class="gf-resolve-selected">
已选目标:{{ selectedDisplayName }}
<span v-if="resolveTargetDrugId()">(ID {{ resolveTargetDrugId() }})</span>
</p>
<div class="gf-preview-toolbar">
<span class="gf-preview-title">
受影响金方预览(已勾选 {{ selectedFormulaCount }} / {{ previewFormulas.length }})
@@ -357,7 +491,13 @@ async function confirmReplace() {
class="gf-preview-table"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'flags'">
<template v-if="column.key === 'match_dose_text'">
{{ record.match_dose_text || '—' }}
</template>
<template v-else-if="column.key === 'split_preview'">
<span class="gf-split-preview">{{ buildSplitPreview(record) }}</span>
</template>
<template v-else-if="column.key === 'flags'">
<Tag v-if="record.is_abnormal === 1" color="error">异常</Tag>
<Tag v-if="record.has_unmatched === 1" color="warning">未匹配</Tag>
<Tag
@@ -374,10 +514,13 @@ async function confirmReplace() {
<Button
type="primary"
:loading="submitting"
:disabled="!selectedDisplayName || !selectedFormulaCount"
:disabled="!validReplaceTargets.length || !selectedFormulaCount"
@click="confirmReplace"
>
确认替换({{ selectedFormulaCount }} 个金方)
确认{{ validReplaceTargets.length > 1 ? '拆分' : '替换' }}({{
selectedFormulaCount
}}
个金方)
</Button>
</Space>
</div>
@@ -401,9 +544,11 @@ async function confirmReplace() {
font-size: 13px;
}
.gf-resolve-search :deep(.chinese-drug-name-bubble),
.gf-resolve-search :deep(.chinese-drug-name-bubble--compact) {
.gf-resolve-search :deep(.chinese-drug-name-bubble--compact),
.gf-replace-search :deep(.chinese-drug-name-bubble),
.gf-replace-search :deep(.chinese-drug-name-bubble--compact) {
width: 100% !important;
min-width: 220px !important;
min-width: 180px !important;
max-width: none !important;
flex: 1 1 auto !important;
}
@@ -412,6 +557,43 @@ async function confirmReplace() {
color: hsl(var(--primary));
font-size: 13px;
}
.gf-replace-targets {
margin-bottom: 16px;
}
.gf-replace-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
padding: 8px 10px;
border: 1px solid hsl(var(--border));
border-radius: 8px;
background: hsl(var(--card, var(--background)));
}
.gf-replace-row.is-active {
border-color: hsl(var(--primary) / 0.55);
background: hsl(var(--primary) / 0.06);
}
.gf-replace-idx {
flex-shrink: 0;
width: 20px;
color: hsl(var(--muted-foreground));
font-size: 12px;
text-align: center;
}
.gf-replace-search {
flex: 1 1 auto;
min-width: 0;
}
.gf-replace-add {
margin-top: 4px;
margin-bottom: 8px;
}
.gf-split-preview {
color: hsl(var(--primary));
font-size: 12px;
line-height: 1.5;
}
.gf-preview-toolbar {
display: flex;
align-items: center;

View File

@@ -272,6 +272,7 @@ async function handleConfirmImport(payload: { rows: any[]; sourceId: number }) {
<Grid>
<template #toolbar-buttons>
<TableAction
:flex="false"
:actions="[
{
label: '新增',

View File

@@ -43,7 +43,7 @@ import SalespersonCommissionDrawer from './components/SalespersonCommissionDrawe
import AddDoctorFromStoreModal from './components/AddDoctorFromStoreModal.vue';
import StoreCardModal from '#/components/store-card/StoreCardModal.vue';
import StoreVipCell from '#/components/vip/StoreVipCell.vue';
import VipUpgradeModal from '#/components/vip/VipUpgradeModal.vue';
import VipHubModal from '#/components/vip/VipHubModal.vue';
import BankCardReportModal from '#/views/system/store-bank-card/components/BankCardReportModal.vue';
import BankCardStatusModal from '#/views/system/store-bank-card/components/BankCardStatusModal.vue';
import BankCardStoreEditModal from '#/views/system/store-bank-card/components/BankCardStoreEditModal.vue';
@@ -105,20 +105,23 @@ const [StoreCardModalComp, StoreCardModalApi] = useVbenModal({
connectedComponent: StoreCardModal,
});
const [VipUpgradeModalComp, vipUpgradeModalApi] = useVbenModal({
connectedComponent: VipUpgradeModal,
const [VipHubModalComp, vipHubModalApi] = useVbenModal({
connectedComponent: VipHubModal,
});
/** 打开门店 VIP 升级弹窗(列表徽标点击) */
function openVipUpgrade(row: Record<string, any>) {
/**
* 打开门店 VIP 入口弹窗(两张卡片:开通/升级、开通记录)
* 为什么先走 Hub:需要同时支持升级操作与查看开通人/流水,避免直接进升级页看不到记录
*/
function openVipHub(row: Record<string, any>) {
if (!row?.id) return;
vipUpgradeModalApi.setData({
vipHubModalApi.setData({
store_id: Number(row.id),
store_name: row.name || '',
vip: row.vip || null,
gridApi,
});
vipUpgradeModalApi.open();
vipHubModalApi.open();
}
/** 添加医生弹窗:诊所列表行操作和详情医生团队共用 */
@@ -491,7 +494,7 @@ const handleSwitchClinicType = (row: any) => {
<BankCardStatusModalComponent />
<BankCardStoreEditModalComponent />
<StoreCardModalComp />
<VipUpgradeModalComp />
<VipHubModalComp />
<AddDoctorModalComp />
<QrCodePreviewModal />
<DrugPriceModalComponent />
@@ -561,7 +564,7 @@ const handleSwitchClinicType = (row: any) => {
/>
</template>
<template #vip="{ row }">
<StoreVipCell :vip="row.vip" @click="openVipUpgrade(row)" />
<StoreVipCell :vip="row.vip" @click="openVipHub(row)" />
</template>
<template #store_config_1="{ row }">
<StoreConfigTogglesCell

View File

@@ -187,10 +187,13 @@ const [Modal, modalApi] = useVbenModal({
{{ (opt.name || opt.code || '?').slice(0, 1) }}
</div>
</div>
<div class="vip-level-card__name">{{ opt.name }}</div>
<div class="vip-level-card__meta">
{{ opt.code }} · 权重 {{ opt.weight }}
<div class="vip-level-card__name">
{{ opt.name || opt.code || '—' }}
<span v-if="opt.code" class="vip-level-card__code">{{
opt.code
}}</span>
</div>
<div class="vip-level-card__meta">权重 {{ opt.weight }}</div>
</div>
</div>
</Modal>
@@ -267,11 +270,21 @@ const [Modal, modalApi] = useVbenModal({
font-weight: 600;
}
.vip-level-card__name {
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: center;
gap: 4px 6px;
font-size: 14px;
font-weight: 600;
color: hsl(var(--foreground));
text-align: center;
}
.vip-level-card__code {
font-size: 12px;
font-weight: 500;
color: hsl(var(--primary));
}
.vip-level-card__meta {
margin-top: 4px;
font-size: 11px;

View File

@@ -81,6 +81,18 @@ const showModal = (data: any = {}, isUpdate = false) => {
formModalApi.open();
};
/**
* 已绑等级展示文案:同名等级(如多档「黄金会员」)需同时带上 code(V1/V2…)才能区分
*/
function formatBoundLevelLabel(lv: Record<string, any>) {
const name = String(lv?.name || '').trim();
const code = String(lv?.code || '').trim();
if (name && code) {
return `${name}(${code})`;
}
return name || code || '—';
}
/** 打开反绑等级弹窗 */
function openBindLevels(item: Record<string, any>) {
bindLevelsModalApi.setData({
@@ -176,20 +188,25 @@ onMounted(loadList);
v-for="lv in item.bound_levels"
:key="lv.id"
class="bound-vip-chip"
:title="lv.name || lv.code"
:title="formatBoundLevelLabel(lv)"
>
<img
v-if="lv.badge_url"
:src="lv.badge_url"
:alt="lv.name || lv.code"
:alt="formatBoundLevelLabel(lv)"
class="bound-vip-chip__img"
/>
<span v-else class="bound-vip-chip__fallback">{{
(lv.name || lv.code || '?').slice(0, 1)
}}</span>
<span class="bound-vip-chip__name">{{
lv.name || lv.code
(lv.code || lv.name || '?').slice(0, 1)
}}</span>
<span class="bound-vip-chip__text">
<span class="bound-vip-chip__name">{{
lv.name || lv.code || '—'
}}</span>
<span v-if="lv.code" class="bound-vip-chip__code">{{
lv.code
}}</span>
</span>
</div>
</div>
<span v-else class="text-xs">未绑定任何会员等级</span>
@@ -238,12 +255,22 @@ onMounted(loadList);
background: hsl(var(--muted) / 0.5);
color: hsl(var(--muted-foreground));
}
.bound-vip-chip__text {
display: inline-flex;
flex-direction: column;
min-width: 0;
line-height: 1.2;
}
.bound-vip-chip__name {
font-size: 12px;
color: hsl(var(--foreground));
max-width: 88px;
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bound-vip-chip__code {
font-size: 11px;
color: hsl(var(--muted-foreground));
}
</style>

View File

@@ -1,13 +1,13 @@
<script lang="ts" setup>
/**
* 门店 VIP 开通页:复用全局 VipUpgradeModal
* 门店 VIP 开通页:点击徽标先进入 VIP 入口(升级 / 开通记录)
*/
import { Page, useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import VipBadgeCombo from '#/components/vip/VipBadgeCombo.vue';
import VipUpgradeModal from '#/components/vip/VipUpgradeModal.vue';
import VipHubModal from '#/components/vip/VipHubModal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
@@ -15,23 +15,24 @@ import { gridOptions } from './config/table';
defineOptions({ name: 'VipStore' });
const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions });
const [UpgradeModalComp, upgradeModalApi] = useVbenModal({
connectedComponent: VipUpgradeModal,
const [HubModalComp, hubModalApi] = useVbenModal({
connectedComponent: VipHubModal,
});
/** 打开 VIP 入口弹窗 */
const openVip = (row: any) => {
upgradeModalApi.setData({
hubModalApi.setData({
store_id: row.store_id,
store_name: row.store_name,
vip: row.vip,
gridApi,
});
upgradeModalApi.open();
hubModalApi.open();
};
</script>
<template>
<Page auto-content-height title="门店VIP开通">
<UpgradeModalComp />
<HubModalComp />
<div class="p-4">
<Grid>
<template #vip_badge="{ row }">
@@ -50,7 +51,7 @@ const openVip = (row: any) => {
</template>
<template #action="{ row }">
<TableAction
:actions="[{ label: '开通/升级', onClick: () => openVip(row) }]"
:actions="[{ label: 'VIP管理', onClick: () => openVip(row) }]"
/>
</template>
</Grid>