fix: 凭证识别
All checks were successful
构建并部署前端到测试环境 / deploy (push) Successful in 54s

This commit is contained in:
luo
2026-09-29 16:07:48 +08:00
parent f896cad34f
commit aec2a27a65
16 changed files with 374 additions and 219 deletions

View File

@@ -10,7 +10,9 @@
- 成员管理弹窗展示当前成员及成员关系信息。
- 候选账号展示 `add`、`already_member`、`move` 操作提示。
- 停用组允许查询和移除,但新增/迁移由后端拒绝并提示。
- 用户组列表/详情使用实时成员数。
- 用户组列表使用接口返回的实时成员数。
- 当前成员和候选账号展示当前所属组名称、编码、启用状态,不展示内部组 ID。
- 店铺列表和详情展示业务用户组编码、名称、启用状态和业务线,不展示内部组 ID。
## Impact

View File

@@ -1,5 +1,32 @@
## ADDED Requirements
### Requirement: Business User Group Metadata Visibility
The frontend SHALL display business user group metadata returned by shop and membership APIs while omitting internal group IDs from these views.
#### Scenario: Show business group fields for shops
- **WHEN** an administrator views the shop list or shop details
- **THEN** the frontend displays the business group code, name, enabled state, and business line returned by the API
- **AND** the frontend does not display the business group ID
#### Scenario: Show current group fields for members and candidates
- **WHEN** an administrator views current members or candidate accounts in the group drawer
- **THEN** the frontend displays each account's current group code, name, and enabled state
- **AND** displays "未分组" when the account has no current group
- **AND** the frontend does not display the current group ID
#### Scenario: Show current member count
- **WHEN** an administrator views the business user group list
- **THEN** the frontend displays the `member_count` returned for each group
#### Scenario: Keep member selection and username visible
- **WHEN** an administrator scrolls horizontally in either member or candidate table
- **THEN** the selection checkbox and username columns remain fixed on the left
### Requirement: Incremental Business User Group Membership
The frontend SHALL use the member query, candidate query, incremental POST add/move, and scoped DELETE remove contracts from the modification document.

View File

@@ -12,3 +12,10 @@
## 3. Verification
- [ ] 3.1 运行类型检查、lint、编码检查、构建和 OpenSpec 校验。
## 4. Metadata Display
- [x] 4.1 在店铺列表和详情中展示业务用户组编码、名称、启用状态及业务线,不展示组 ID。
- [x] 4.2 在成员与候选账号表中展示当前组编码、名称及启用状态,不展示组 ID。
- [x] 4.3 在业务用户组列表中展示接口返回的成员数。
- [x] 4.4 成员和候选账号表横向滚动时固定勾选框与用户名列在左侧。

View File

@@ -160,6 +160,11 @@
.filter(Boolean)
: []
const currentKeys = Array.from(voucherFileKeyMap.values())
const isEchoOfCurrentValue =
keys.length === currentKeys.length && keys.every((key, index) => key === currentKeys[index])
if (isEchoOfCurrentValue) return
voucherFileKeyMap.clear()
keys.slice(0, getMaxCount()).forEach((key, index) => {
voucherFileKeyMap.set(index + 1, key)

View File

@@ -646,164 +646,166 @@ export const asyncRoutes: AppRouteRecord[] = [
permissions: [BULK_PURCHASE_PERMISSIONS.detail],
keepAlive: false
}
},
// 导出管理
}
]
},
// 导出管理
{
path: '/asset-management/export-task-management',
name: 'ExportTaskManagement',
component: RoutesAlias.Home,
meta: {
title: 'menus.assetManagement.exportTaskManagement',
icon: '',
keepAlive: true
},
children: [
{
path: 'export-task-management',
name: 'ExportTaskManagement',
component: '',
path: 'export-device',
name: 'ExportDeviceTaskList',
component: RoutesAlias.ExportDeviceTaskList,
meta: {
title: 'menus.assetManagement.exportTaskManagement',
title: 'menus.assetManagement.exportDevice',
exportTaskScene: 'device',
keepAlive: true
},
children: [
{
path: 'export-device',
name: 'ExportDeviceTaskList',
component: RoutesAlias.ExportDeviceTaskList,
meta: {
title: 'menus.assetManagement.exportDevice',
exportTaskScene: 'device',
keepAlive: true
}
},
{
path: 'export-iot-card',
name: 'ExportIotCardTaskList',
component: RoutesAlias.ExportIotCardTaskList,
meta: {
title: 'menus.assetManagement.exportIotCard',
exportTaskScene: 'iot_card',
keepAlive: true
}
},
{
path: 'export-order',
name: 'ExportOrderTaskList',
component: RoutesAlias.ExportOrderTaskList,
meta: {
title: 'menus.assetManagement.exportOrder',
exportTaskScene: 'order',
keepAlive: true
}
},
{
path: 'export-package',
name: 'ExportPackageTaskList',
component: RoutesAlias.ExportPackageTaskList,
meta: {
title: 'menus.assetManagement.exportPackage',
exportTaskScene: 'package',
keepAlive: true
}
},
{
path: 'export-agent-wallet-transaction',
name: 'ExportAgentWalletTransactionTaskList',
component: RoutesAlias.ExportAgentWalletTransactionTaskList,
meta: {
title: 'menus.assetManagement.exportAgentWalletTransaction',
exportTaskScene: 'agent_wallet_transaction',
keepAlive: true
}
},
{
path: 'export-refund',
name: 'ExportRefundTaskList',
component: RoutesAlias.ExportRefundTaskList,
meta: {
title: 'menus.assetManagement.exportRefund',
exportTaskScene: 'refund',
keepAlive: true
}
},
{
path: 'export-agent-recharge',
name: 'ExportAgentRechargeTaskList',
component: RoutesAlias.ExportAgentRechargeTaskList,
meta: {
title: 'menus.assetManagement.exportAgentRecharge',
exportTaskScene: 'agent_recharge',
keepAlive: true
}
},
{
path: 'export-exchange',
name: 'ExportExchangeTaskList',
component: RoutesAlias.ExportExchangeTaskList,
meta: {
title: 'menus.assetManagement.exportExchange',
exportTaskScene: 'exchange',
keepAlive: true
}
},
{
path: 'export-commission-record',
name: 'ExportCommissionRecordTaskList',
component: RoutesAlias.ExportCommissionRecordTaskList,
meta: {
title: 'menus.assetManagement.exportCommissionRecord',
exportTaskScene: 'commission_record',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.commission_record.permissions.page]
}
},
{
path: 'export-package-traffic-alert',
name: 'ExportPackageTrafficAlertTaskList',
component: RoutesAlias.ExportPackageTrafficAlertTaskList,
meta: {
title: 'menus.assetManagement.exportPackageTrafficAlert',
exportTaskScene: 'package_traffic_alert',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.package_traffic_alert.permissions.page]
}
},
{
path: 'export-expiring-asset',
name: 'ExportExpiringAssetTaskList',
component: RoutesAlias.ExportExpiringAssetTaskList,
meta: {
title: 'menus.assetManagement.exportExpiringAsset',
exportTaskScene: 'expiring_asset',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.expiring_asset.permissions.page]
}
},
{
path: 'export-operations-activation',
name: 'ExportOperationsActivationTaskList',
component: RoutesAlias.ExportOperationsActivationTaskList,
meta: {
title: 'menus.assetManagement.exportOperationsActivation',
exportTaskScene: 'operations_activation',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.operations_activation.permissions.page]
}
},
{
path: 'export-operations-renewal',
name: 'ExportOperationsRenewalTaskList',
component: RoutesAlias.ExportOperationsRenewalTaskList,
meta: {
title: 'menus.assetManagement.exportOperationsRenewal',
exportTaskScene: 'operations_renewal',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.operations_renewal.permissions.page]
}
},
{
path: 'export-task-detail',
name: 'ExportTaskDetail',
component: RoutesAlias.ExportTaskDetail,
meta: {
title: 'menus.assetManagement.exportTaskDetail',
isHide: true,
keepAlive: false,
permissions: [EXPORT_TASK_DETAIL_PAGE_PERMISSION]
}
}
]
}
},
{
path: 'export-iot-card',
name: 'ExportIotCardTaskList',
component: RoutesAlias.ExportIotCardTaskList,
meta: {
title: 'menus.assetManagement.exportIotCard',
exportTaskScene: 'iot_card',
keepAlive: true
}
},
{
path: 'export-order',
name: 'ExportOrderTaskList',
component: RoutesAlias.ExportOrderTaskList,
meta: {
title: 'menus.assetManagement.exportOrder',
exportTaskScene: 'order',
keepAlive: true
}
},
{
path: 'export-package',
name: 'ExportPackageTaskList',
component: RoutesAlias.ExportPackageTaskList,
meta: {
title: 'menus.assetManagement.exportPackage',
exportTaskScene: 'package',
keepAlive: true
}
},
{
path: 'export-agent-wallet-transaction',
name: 'ExportAgentWalletTransactionTaskList',
component: RoutesAlias.ExportAgentWalletTransactionTaskList,
meta: {
title: 'menus.assetManagement.exportAgentWalletTransaction',
exportTaskScene: 'agent_wallet_transaction',
keepAlive: true
}
},
{
path: 'export-refund',
name: 'ExportRefundTaskList',
component: RoutesAlias.ExportRefundTaskList,
meta: {
title: 'menus.assetManagement.exportRefund',
exportTaskScene: 'refund',
keepAlive: true
}
},
{
path: 'export-agent-recharge',
name: 'ExportAgentRechargeTaskList',
component: RoutesAlias.ExportAgentRechargeTaskList,
meta: {
title: 'menus.assetManagement.exportAgentRecharge',
exportTaskScene: 'agent_recharge',
keepAlive: true
}
},
{
path: 'export-exchange',
name: 'ExportExchangeTaskList',
component: RoutesAlias.ExportExchangeTaskList,
meta: {
title: 'menus.assetManagement.exportExchange',
exportTaskScene: 'exchange',
keepAlive: true
}
},
{
path: 'export-commission-record',
name: 'ExportCommissionRecordTaskList',
component: RoutesAlias.ExportCommissionRecordTaskList,
meta: {
title: 'menus.assetManagement.exportCommissionRecord',
exportTaskScene: 'commission_record',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.commission_record.permissions.page]
}
},
{
path: 'export-package-traffic-alert',
name: 'ExportPackageTrafficAlertTaskList',
component: RoutesAlias.ExportPackageTrafficAlertTaskList,
meta: {
title: 'menus.assetManagement.exportPackageTrafficAlert',
exportTaskScene: 'package_traffic_alert',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.package_traffic_alert.permissions.page]
}
},
{
path: 'export-expiring-asset',
name: 'ExportExpiringAssetTaskList',
component: RoutesAlias.ExportExpiringAssetTaskList,
meta: {
title: 'menus.assetManagement.exportExpiringAsset',
exportTaskScene: 'expiring_asset',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.expiring_asset.permissions.page]
}
},
{
path: 'export-operations-activation',
name: 'ExportOperationsActivationTaskList',
component: RoutesAlias.ExportOperationsActivationTaskList,
meta: {
title: 'menus.assetManagement.exportOperationsActivation',
exportTaskScene: 'operations_activation',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.operations_activation.permissions.page]
}
},
{
path: 'export-operations-renewal',
name: 'ExportOperationsRenewalTaskList',
component: RoutesAlias.ExportOperationsRenewalTaskList,
meta: {
title: 'menus.assetManagement.exportOperationsRenewal',
exportTaskScene: 'operations_renewal',
keepAlive: true,
permissions: [EXPORT_TASK_SCENE_CONFIG.operations_renewal.permissions.page]
}
},
{
path: 'export-task-detail',
name: 'ExportTaskDetail',
component: RoutesAlias.ExportTaskDetail,
meta: {
title: 'menus.assetManagement.exportTaskDetail',
isHide: true,
keepAlive: false,
permissions: [EXPORT_TASK_DETAIL_PAGE_PERMISSION]
}
}
]
},

View File

@@ -91,6 +91,7 @@ export interface BusinessUserGroupMember {
current_group_id?: number | null
current_group_code?: string | null
current_group_name?: string | null
current_group_enabled?: boolean | null
operation?: 'add' | 'already_member' | 'move' | string
operation_name?: string | null
}

View File

@@ -123,6 +123,7 @@ declare module 'vue' {
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElResult: typeof import('element-plus/es')['ElResult']

View File

@@ -10,11 +10,6 @@
返回
</ElButton>
<h2 class="detail-title">导出任务详情</h2>
<div class="detail-actions" v-if="taskDetail && canViewDetail">
<ElButton v-if="canDownloadTask" type="primary" @click="downloadTask">
下载文件
</ElButton>
</div>
</div>
<div v-if="polling.loading && !taskDetail" class="detail-loading">
@@ -203,8 +198,7 @@
label: '下载地址',
fullWidth: true,
render: (data: ExportTaskDetail) => {
if (!data.download_url) return h('span', '-')
if (!canDownloadTask.value) return h('span', '-')
if (!data.download_url || !canDownloadTask.value) return h('span', '-')
return h(
ElButton,
{
@@ -276,12 +270,6 @@
font-weight: 600;
color: var(--el-text-color-primary);
}
.detail-actions {
display: flex;
gap: 10px;
margin-left: auto;
}
}
.detail-loading {

View File

@@ -96,7 +96,6 @@
const taskDetail = ref<TaskDetail | null>(null)
const taskType = ref<TaskType>('card')
const loading = ref(false)
const getOperationLabel = (operationType?: DeviceImportTaskOperationType) => {
switch (operationType) {
case 'assign_shop':

View File

@@ -129,7 +129,7 @@
<ElButton
:loading="ocrLoading"
:disabled="!offlineVoucherKeys.length"
@click="handleRecognizeVoucher"
@click="handleRecognizeVoucher()"
>
识别凭证
</ElButton>
@@ -359,7 +359,7 @@
</template>
<script setup lang="ts">
import { h } from 'vue'
import { h, watch } from 'vue'
import { useRouter } from 'vue-router'
import QrcodeVue from 'qrcode.vue'
import {
@@ -483,6 +483,8 @@
const otherVoucherUploading = ref(false)
const ocrLoading = ref(false)
const recognizedVoucherKey = ref('')
const recognizedTransactionNo = ref('')
const ocrRequestId = ref(0)
// 搜索表单初始值
const initialSearchState: AgentRechargeQueryParams = {
@@ -808,7 +810,7 @@
prop: 'recharge_no',
label: '充值单号',
minWidth: 240,
fixed:true,
fixed: true,
formatter: (row: AgentRecharge) => {
return h(
'span',
@@ -1131,6 +1133,9 @@
voucherUploading.value = false
otherVoucherUploading.value = false
ocrLoading.value = false
ocrRequestId.value += 1
recognizedVoucherKey.value = ''
recognizedTransactionNo.value = ''
uploadRef.value?.clearFiles(false)
otherUploadRef.value?.clearFiles(false)
}
@@ -1188,36 +1193,45 @@
// 已上传的支付凭证对象键(OCR 识别取第一个)
const offlineVoucherKeys = computed(() => toVoucherKeyList(createForm.payment_voucher_key))
// 凭证变更:校验字段,并对首次上传自动识别一次(已有流水号则跳过)
// 凭证变更时取消过期识别;只有清除 OCR 自动填入的值,保留人工填写内容。
const handleVoucherChange = (keys: string[] | string) => {
createFormRef.value?.validateField('payment_voucher_key')
const list = toVoucherKeyList(keys)
const firstKey = list[0] || ''
if (!firstKey) {
recognizedVoucherKey.value = ''
return
}
if (firstKey === recognizedVoucherKey.value) return
ocrRequestId.value += 1
ocrLoading.value = false
if (
recognizedTransactionNo.value &&
createForm.external_transaction_no.trim() === recognizedTransactionNo.value
) {
createForm.external_transaction_no = ''
createFormRef.value?.validateField('external_transaction_no')
}
recognizedTransactionNo.value = ''
recognizedVoucherKey.value = firstKey
if (createForm.external_transaction_no?.trim()) return
void handleRecognizeVoucher()
if (!firstKey || createForm.external_transaction_no?.trim()) return
void handleRecognizeVoucher(list)
}
// 识别付款凭证中的交易流水号:只做预填,失败不阻断人工填写
const handleRecognizeVoucher = async () => {
const handleRecognizeVoucher = async (keysOverride?: string[]) => {
if (ocrLoading.value) return
const voucherKeys = offlineVoucherKeys.value
const voucherKeys = keysOverride?.length ? keysOverride : offlineVoucherKeys.value
if (!voucherKeys.length) {
ElMessage.warning('请先上传支付凭证')
return
}
const requestId = ++ocrRequestId.value
ocrLoading.value = true
try {
const res = await AgentRechargeService.recognizePaymentVoucher({
payment_voucher_key: voucherKeys[0]
})
if (requestId !== ocrRequestId.value) return
if (res.code !== 0) {
ElMessage.warning(res.msg || '识别失败,请手动填写交易流水号')
return
@@ -1227,14 +1241,16 @@
ElMessage.warning('未识别到交易流水号,请手动填写')
return
}
recognizedTransactionNo.value = transactionNo
createForm.external_transaction_no = transactionNo
createFormRef.value?.validateField('external_transaction_no')
ElMessage.success('已识别交易流水号,请对照凭证核对')
} catch (error) {
if (requestId !== ocrRequestId.value) return
console.error('识别付款凭证失败:', error)
ElMessage.warning('识别失败,请手动填写交易流水号')
} finally {
ocrLoading.value = false
if (requestId === ocrRequestId.value) ocrLoading.value = false
}
}

View File

@@ -361,6 +361,8 @@
const voucherUploading = ref(false)
const ocrLoading = ref(false)
const recognizedVoucherKey = ref('')
const recognizedTransactionNo = ref('')
const ocrRequestId = ref(0)
const paymentMethods = ref<EmployeeCollectionPaymentMethod[]>([])
const candidateBills = ref<BillOption[]>([])
const selectedBillOptions = ref<Record<number, BillOption>>({})
@@ -678,6 +680,9 @@
candidateBills.value = []
shopOptions.value = []
recognizedVoucherKey.value = ''
recognizedTransactionNo.value = ''
ocrRequestId.value += 1
ocrLoading.value = false
uploadRef.value?.clearFiles()
}
@@ -761,19 +766,20 @@
}
const recognizeVoucher = async (keysOverride?: string[]): Promise<void> => {
if (ocrLoading.value) return
const keys = keysOverride?.length ? keysOverride : voucherKeys.value
if (!keys.length) {
ElMessage.warning('请先上传付款凭证')
return
}
if (ocrLoading.value) return
const requestId = ++ocrRequestId.value
ocrLoading.value = true
try {
const res = await AgentRechargeService.recognizePaymentVoucher({
payment_voucher_key: keys[0]
})
if (requestId !== ocrRequestId.value) return
if (res.code !== 0) {
ElMessage.warning(res.msg || '识别失败,请手动填写交易流水号')
return
@@ -783,14 +789,16 @@
ElMessage.warning('未识别到交易流水号,请手动填写')
return
}
recognizedTransactionNo.value = transactionNo
form.external_transaction_no = transactionNo
formRef.value?.validateField('external_transaction_no')
ElMessage.success('已识别交易流水号,请对照凭证核对')
} catch (error) {
if (requestId !== ocrRequestId.value) return
console.error('识别付款凭证失败:', error)
ElMessage.warning('识别失败,请手动填写交易流水号')
} finally {
ocrLoading.value = false
if (requestId === ocrRequestId.value) ocrLoading.value = false
}
}
@@ -802,13 +810,20 @@
formRef.value?.validateField('payment_voucher_keys')
const list = toVoucherKeyList(keys)
const firstKey = list[0] || ''
if (!firstKey) {
recognizedVoucherKey.value = ''
return
}
if (firstKey === recognizedVoucherKey.value) return
ocrRequestId.value += 1
ocrLoading.value = false
if (
recognizedTransactionNo.value &&
form.external_transaction_no.trim() === recognizedTransactionNo.value
) {
form.external_transaction_no = ''
formRef.value?.validateField('external_transaction_no')
}
recognizedTransactionNo.value = ''
recognizedVoucherKey.value = firstKey
if (form.external_transaction_no.trim()) return
if (!firstKey || form.external_transaction_no.trim()) return
void recognizeVoucher(list)
}

View File

@@ -274,7 +274,8 @@
{
prop: 'creator_name',
label: '创建人',
width: 100
width: 140,
showOverflowTooltip: true
},
{
prop: 'status',

View File

@@ -139,9 +139,33 @@
v-loading="memberCandidatesLoading"
@selection-change="candidateSelection = $event"
>
<ElTableColumn v-if="memberCandidates.length" type="selection" width="42" />
<ElTableColumn prop="username" label="用户名" min-width="120" />
<ElTableColumn
v-if="memberCandidates.length"
type="selection"
width="42"
fixed="left"
/>
<ElTableColumn prop="username" label="用户名" min-width="120" fixed="left" />
<ElTableColumn prop="phone" label="手机号" min-width="110" />
<ElTableColumn label="当前所属业务用户组" min-width="210" show-overflow-tooltip>
<template #default="scope">
<div v-if="scope.row.current_group_id != null" class="member-current-group">
<span
>{{ scope.row.current_group_name || '-' }}({{
scope.row.current_group_code || '-'
}})</span
>
<ElTag
v-if="scope.row.current_group_enabled != null"
:type="scope.row.current_group_enabled ? 'success' : 'danger'"
size="small"
>
{{ scope.row.current_group_enabled ? '启用' : '已停用' }}
</ElTag>
</div>
<span v-else>未分组</span>
</template>
</ElTableColumn>
<ElTableColumn label="操作提示" min-width="130" show-overflow-tooltip>
<template #default="scope">
<ElTag size="small" type="info">
@@ -177,10 +201,34 @@
empty-text="当前业务组暂无成员"
@selection-change="clearSelection = $event"
>
<ElTableColumn v-if="filteredCurrentMembers.length" type="selection" width="42" />
<ElTableColumn prop="username" label="用户名" min-width="120" />
<ElTableColumn
v-if="filteredCurrentMembers.length"
type="selection"
width="42"
fixed="left"
/>
<ElTableColumn prop="username" label="用户名" min-width="120" fixed="left" />
<ElTableColumn prop="phone" label="手机号" min-width="110" />
<ElTableColumn prop="status_name" label="状态" width="90" />
<ElTableColumn label="当前所属业务用户组" min-width="210" show-overflow-tooltip>
<template #default="scope">
<div v-if="scope.row.current_group_id != null" class="member-current-group">
<span
>{{ scope.row.current_group_name || '-' }}({{
scope.row.current_group_code || '-'
}})</span
>
<ElTag
v-if="scope.row.current_group_enabled != null"
:type="scope.row.current_group_enabled ? 'success' : 'danger'"
size="small"
>
{{ scope.row.current_group_enabled ? '启用' : '已停用' }}
</ElTag>
</div>
<span v-else>未分组</span>
</template>
</ElTableColumn>
</ElTable>
<ElButton type="danger" :loading="clearingMembers" @click="handleClearMembers">
移出所选成员
@@ -276,6 +324,7 @@
{ label: '稳定编码', prop: 'code', minWidth: 140, showOverflowTooltip: true },
{ label: '名称', prop: 'name', minWidth: 160, showOverflowTooltip: true },
{ label: '业务线', prop: 'business_line', width: 110 },
{ label: '成员数', prop: 'member_count', width: 90 },
{ label: '状态', prop: 'enabled', width: 90 },
{ label: '排序', prop: 'sort', width: 80 },
{ label: '备注', prop: 'remark', minWidth: 160, showOverflowTooltip: true },
@@ -306,6 +355,12 @@
return config ? config.label : row.business_line
}
},
{
prop: 'member_count',
label: '成员数',
width: 90,
formatter: (row: BusinessUserGroupItem) => row.member_count ?? 0
},
{
prop: 'enabled',
label: '状态',

View File

@@ -36,11 +36,29 @@
: '-'
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="业务用户组">
{{ formatBusinessUserGroup(shop) }}
<ElDescriptionsItem label="业务用户组编码">
{{ shop.business_user_group_id == null ? '-' : shop.business_user_group_code || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="业务用户组名称">
{{
shop.business_user_group_id == null ? '未分组' : shop.business_user_group_name || '-'
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="业务用户组状态">
{{
shop.business_user_group_id == null
? '-'
: shop.business_user_group_enabled
? '启用'
: '已停用'
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="业务用户组业务线">
{{ getBusinessUserGroupBusinessLineLabel(shop.business_user_group_business_line) }}
{{
shop.business_user_group_id == null
? '-'
: getBusinessUserGroupBusinessLineLabel(shop.business_user_group_business_line)
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="省份">{{ shop.province || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="城市">{{ shop.city || '-' }}</ElDescriptionsItem>
@@ -99,14 +117,6 @@
: value.business_owner_username
}
const formatBusinessUserGroup = (value: ShopResponse) => {
if (!value.business_user_group_id) return '-'
const text = value.business_user_group_code
? `${value.business_user_group_name || '-'}(${value.business_user_group_code})`
: value.business_user_group_name || '-'
return value.business_user_group_enabled ? text : `${text}(已停用)`
}
const handleBack = () => router.back()
const fetchDetail = async () => {

View File

@@ -746,7 +746,10 @@
{ label: '联系人', prop: 'contact_name' },
{ label: '联系电话', prop: 'contact_phone' },
{ label: '平台业务员', prop: 'business_owner_username' },
{ label: '业务用户组', prop: 'business_user_group_name' },
{ label: '业务用户组编码', prop: 'business_user_group_code' },
{ label: '业务用户组名称', prop: 'business_user_group_name' },
{ label: '业务用户组状态', prop: 'business_user_group_enabled' },
{ label: '业务用户组业务线', prop: 'business_user_group_business_line' },
...(canModifyShopStatus ? [{ label: '状态', prop: 'status' }] : []),
{ label: '创建时间', prop: 'created_at' },
{ label: '操作', prop: 'operation' }
@@ -843,7 +846,7 @@
prop: 'shop_name',
label: '店铺名称',
width: 160,
fixed:true,
fixed: true,
showOverflowTooltip: true,
formatter: (row: ShopResponse) => {
const hasPermission = hasAuth('shop:detail')
@@ -909,21 +912,43 @@
formatter: (row: ShopResponse) => formatBusinessOwner(row)
},
{
prop: 'business_user_group_name',
label: '业务用户组',
minWidth: 160,
prop: 'business_user_group_code',
label: '业务用户组编码',
minWidth: 150,
showOverflowTooltip: true,
formatter: (row: ShopResponse) =>
row.business_user_group_id == null ? '-' : row.business_user_group_code || '-'
},
{
prop: 'business_user_group_name',
label: '业务用户组名称',
minWidth: 150,
showOverflowTooltip: true,
formatter: (row: ShopResponse) =>
row.business_user_group_id == null ? '未分组' : row.business_user_group_name || '-'
},
{
prop: 'business_user_group_enabled',
label: '业务用户组状态',
width: 120,
formatter: (row: ShopResponse) => {
if (!row.business_user_group_id) return '-'
const name = row.business_user_group_name || row.business_user_group_code || '-'
const businessLine = getBusinessUserGroupBusinessLineLabel(
row.business_user_group_business_line
if (row.business_user_group_id == null) return '-'
return h(
ElTag,
{ type: row.business_user_group_enabled ? 'success' : 'danger', size: 'small' },
() => (row.business_user_group_enabled ? '启用' : '已停用')
)
const text = businessLine && businessLine !== '-' ? `${name}(${businessLine})` : name
if (row.business_user_group_enabled) return text
return h(ElTag, { type: 'danger', size: 'small' }, { default: () => `${text}(已停用)` })
}
},
{
prop: 'business_user_group_business_line',
label: '业务用户组业务线',
minWidth: 140,
formatter: (row: ShopResponse) =>
row.business_user_group_id == null
? '-'
: getBusinessUserGroupBusinessLineLabel(row.business_user_group_business_line)
},
{
prop: 'client_login_disabled',
label: 'C 端登录',

View File

@@ -380,6 +380,7 @@
{
type: 'primary',
link: true,
style: { textDecoration: 'underline' },
onClick: () => void router.push(`${RoutesAlias.CarrierDetail}/${row.id}`)
},
() => row.carrier_name || '-'