This commit is contained in:
@@ -10,7 +10,9 @@
|
||||
- 成员管理弹窗展示当前成员及成员关系信息。
|
||||
- 候选账号展示 `add`、`already_member`、`move` 操作提示。
|
||||
- 停用组允许查询和移除,但新增/迁移由后端拒绝并提示。
|
||||
- 用户组列表/详情使用实时成员数。
|
||||
- 用户组列表使用接口返回的实时成员数。
|
||||
- 当前成员和候选账号展示当前所属组名称、编码、启用状态,不展示内部组 ID。
|
||||
- 店铺列表和详情展示业务用户组编码、名称、启用状态和业务线,不展示内部组 ID。
|
||||
|
||||
## Impact
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 成员和候选账号表横向滚动时固定勾选框与用户名列在左侧。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,8 @@
|
||||
{
|
||||
prop: 'creator_name',
|
||||
label: '创建人',
|
||||
width: 100
|
||||
width: 140,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
|
||||
@@ -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: '状态',
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 端登录',
|
||||
|
||||
@@ -380,6 +380,7 @@
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
style: { textDecoration: 'underline' },
|
||||
onClick: () => void router.push(`${RoutesAlias.CarrierDetail}/${row.id}`)
|
||||
},
|
||||
() => row.carrier_name || '-'
|
||||
|
||||
Reference in New Issue
Block a user