This commit is contained in:
@@ -100,18 +100,6 @@
|
||||
当前暂无可用在线支付方式,暂时无法提交充值。
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="createMode === 'offline'" label="店铺" prop="shop_id">
|
||||
<ElCascader
|
||||
v-model="createForm.shop_id"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择店铺"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleShopChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="createForm.payment_method === 'offline'"
|
||||
label="支付凭证"
|
||||
@@ -127,26 +115,6 @@
|
||||
@change="handleVoucherChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="createMode === 'offline'"
|
||||
label="收款方式"
|
||||
prop="offline_payment_method_id"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="createForm.offline_payment_method_id"
|
||||
placeholder="请选择收款方式"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
:loading="paymentMethodOptionsLoading"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in offlinePaymentMethodOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="createForm.payment_method === 'offline'"
|
||||
label="交易流水号"
|
||||
@@ -169,6 +137,47 @@
|
||||
<div class="external-transaction-tip">
|
||||
识别结果仅供参考,请对照凭证核对后再提交。
|
||||
</div>
|
||||
<div v-if="ocrLoading" class="ocr-progress">
|
||||
<ElProgress
|
||||
:percentage="100"
|
||||
:indeterminate="true"
|
||||
:show-text="false"
|
||||
:stroke-width="10"
|
||||
/>
|
||||
<div class="ocr-progress__text">正在识别支付凭证,请稍候...</div>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="createMode === 'offline'" label="店铺" prop="shop_id">
|
||||
<ElCascader
|
||||
v-model="createForm.shop_id"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择店铺"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleShopChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="createMode === 'offline'"
|
||||
label="收款方式"
|
||||
prop="offline_payment_method_id"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="createForm.offline_payment_method_id"
|
||||
placeholder="请选择收款方式"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
:loading="paymentMethodOptionsLoading"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in offlinePaymentMethodOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="createMode === 'offline'" label="其他凭证">
|
||||
<VoucherUpload
|
||||
@@ -1624,6 +1633,17 @@
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.ocr-progress {
|
||||
margin-top: 10px;
|
||||
|
||||
&__text {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.external-transaction-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
@uploading-change="voucherUploading = $event"
|
||||
@change="handleVoucherChange"
|
||||
/>
|
||||
<div v-if="ocrLoading" class="ocr-progress">
|
||||
<div class="ocr-progress__text">正在识别付款凭证,请稍候...</div>
|
||||
<ElProgress
|
||||
:percentage="100"
|
||||
:indeterminate="true"
|
||||
:show-text="false"
|
||||
:stroke-width="8"
|
||||
/>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="外部交易流水号" prop="external_transaction_no">
|
||||
@@ -67,81 +76,106 @@
|
||||
<ElFormItem label="核销账单" required>
|
||||
<div class="bill-selector">
|
||||
<div class="bill-selector__filters">
|
||||
<div class="bill-selector__row">
|
||||
<ElSelect
|
||||
v-model="billSearch.source_type"
|
||||
placeholder="来源"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
>
|
||||
<ElOption
|
||||
v-for="opt in BILL_SOURCE_OPTIONS"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
<ElRow :gutter="8" class="bill-selector__row">
|
||||
<ElCol :span="6">
|
||||
<ElSelect
|
||||
v-model="billSearch.source_type"
|
||||
placeholder="来源"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
>
|
||||
<ElOption
|
||||
v-for="opt in BILL_SOURCE_OPTIONS"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElInput
|
||||
v-model="billSearch.source_no"
|
||||
placeholder="来源单号"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
/>
|
||||
</ElSelect>
|
||||
<ElInput
|
||||
v-model="billSearch.source_no"
|
||||
placeholder="来源单号"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
/>
|
||||
<ElSelect
|
||||
v-model="billSearch.status"
|
||||
placeholder="核销状态"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
>
|
||||
<ElOption
|
||||
v-for="opt in BILL_STATUS_OPTIONS"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElSelect
|
||||
v-model="billSearch.status"
|
||||
placeholder="核销状态"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
>
|
||||
<ElOption
|
||||
v-for="opt in BILL_STATUS_OPTIONS"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElSelect
|
||||
v-model="billSearch.customer_id"
|
||||
placeholder="店铺"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchBillShops"
|
||||
class="bill-selector__filter"
|
||||
>
|
||||
<ElOption
|
||||
v-for="shop in shopOptions"
|
||||
:key="shop.id"
|
||||
:label="shop.shop_name"
|
||||
:value="shop.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="8" class="bill-selector__row">
|
||||
<ElCol :span="6">
|
||||
<ElDatePicker
|
||||
v-model="billSearch.start_time"
|
||||
type="date"
|
||||
placeholder="开始日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
style="width: 100%; min-width: 0"
|
||||
/>
|
||||
</ElSelect>
|
||||
<ElSelect
|
||||
v-model="billSearch.customer_id"
|
||||
placeholder="店铺"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchBillShops"
|
||||
class="bill-selector__filter"
|
||||
>
|
||||
<ElOption
|
||||
v-for="shop in shopOptions"
|
||||
:key="shop.id"
|
||||
:label="shop.shop_name"
|
||||
:value="shop.id"
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElDatePicker
|
||||
v-model="billSearch.end_time"
|
||||
type="date"
|
||||
placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
class="bill-selector__filter"
|
||||
style="width: 100%; min-width: 0"
|
||||
/>
|
||||
</ElSelect>
|
||||
</div>
|
||||
<div class="bill-selector__row">
|
||||
<ElDatePicker
|
||||
v-model="billSearch.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="bill-selector__filter bill-selector__filter--span2"
|
||||
/>
|
||||
<ElButton
|
||||
class="bill-selector__filter bill-selector__action"
|
||||
@click="handleBillReset"
|
||||
>
|
||||
重置
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
class="bill-selector__filter bill-selector__action"
|
||||
@click="handleBillSearch"
|
||||
>
|
||||
搜索
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElButton
|
||||
class="bill-selector__filter bill-selector__action"
|
||||
@click="handleBillReset"
|
||||
>
|
||||
重置
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElButton
|
||||
type="primary"
|
||||
class="bill-selector__filter bill-selector__action"
|
||||
@click="handleBillSearch"
|
||||
>
|
||||
搜索
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
|
||||
<div v-if="billsLoading" class="bill-selector__empty">加载中...</div>
|
||||
@@ -340,9 +374,8 @@
|
||||
source_no: '',
|
||||
status: undefined as number | undefined,
|
||||
customer_id: undefined as number | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_from: '',
|
||||
created_to: ''
|
||||
start_time: '',
|
||||
end_time: ''
|
||||
})
|
||||
|
||||
const billPagination = reactive({
|
||||
@@ -520,8 +553,8 @@
|
||||
source_no: billSearch.source_no.trim() || undefined,
|
||||
status: billSearch.status,
|
||||
customer_id: billSearch.customer_id,
|
||||
created_from: billSearch.created_from || undefined,
|
||||
created_to: billSearch.created_to || undefined
|
||||
start_time: billSearch.start_time || undefined,
|
||||
end_time: billSearch.end_time || undefined
|
||||
})
|
||||
|
||||
const loadCandidateBills = async () => {
|
||||
@@ -565,18 +598,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const syncBillCreatedRange = () => {
|
||||
if (Array.isArray(billSearch.dateRange) && billSearch.dateRange.length === 2) {
|
||||
billSearch.created_from = billSearch.dateRange[0]
|
||||
billSearch.created_to = billSearch.dateRange[1]
|
||||
} else {
|
||||
billSearch.created_from = ''
|
||||
billSearch.created_to = ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleBillSearch = () => {
|
||||
syncBillCreatedRange()
|
||||
billPagination.page = 1
|
||||
void loadCandidateBills()
|
||||
}
|
||||
@@ -586,9 +608,8 @@
|
||||
billSearch.source_no = ''
|
||||
billSearch.status = undefined
|
||||
billSearch.customer_id = undefined
|
||||
billSearch.dateRange = []
|
||||
billSearch.created_from = ''
|
||||
billSearch.created_to = ''
|
||||
billSearch.start_time = ''
|
||||
billSearch.end_time = ''
|
||||
billPagination.page = 1
|
||||
void loadCandidateBills()
|
||||
}
|
||||
@@ -650,9 +671,8 @@
|
||||
billSearch.source_no = ''
|
||||
billSearch.status = undefined
|
||||
billSearch.customer_id = undefined
|
||||
billSearch.dateRange = []
|
||||
billSearch.created_from = ''
|
||||
billSearch.created_to = ''
|
||||
billSearch.start_time = ''
|
||||
billSearch.end_time = ''
|
||||
billPagination.page = 1
|
||||
billPagination.total = 0
|
||||
candidateBills.value = []
|
||||
@@ -863,10 +883,7 @@
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__filter {
|
||||
@@ -874,10 +891,6 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__filter--span2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
&__action {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -953,4 +966,20 @@
|
||||
line-height: 18px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.ocr-progress {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-7);
|
||||
border-radius: 8px;
|
||||
|
||||
&__text {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -56,15 +56,14 @@
|
||||
<template #header>
|
||||
<div class="block-title">付款凭证</div>
|
||||
</template>
|
||||
<ElButton
|
||||
<PaymentVoucherDialog
|
||||
v-if="
|
||||
voucherKeys.length &&
|
||||
hasAuth(AUGUST_PERMISSIONS.employeeCollection.applicationViewVoucher)
|
||||
"
|
||||
@click="voucherVisible = true"
|
||||
>
|
||||
查看付款凭证
|
||||
</ElButton>
|
||||
inline
|
||||
:file-keys="voucherKeys"
|
||||
/>
|
||||
<span v-else class="empty-text">暂无付款凭证</span>
|
||||
</ElCard>
|
||||
|
||||
@@ -102,8 +101,6 @@
|
||||
</ElTimelineItem>
|
||||
</ElTimeline>
|
||||
</ElCard>
|
||||
|
||||
<PaymentVoucherDialog :file-keys="voucherKeys" @close="voucherVisible = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -150,7 +147,6 @@
|
||||
|
||||
const loading = ref(false)
|
||||
const detail = ref<EmployeeCollectionApplicationDetailData | null>(null)
|
||||
const voucherVisible = ref(false)
|
||||
|
||||
const application = computed<EmployeeCollectionApplication | null>(
|
||||
() => detail.value?.application || null
|
||||
@@ -169,7 +165,11 @@
|
||||
{
|
||||
title: '申请信息',
|
||||
fields: [
|
||||
{ label: '申请编号', prop: 'id', formatter: (value) => (value ? `#${value}` : '-') },
|
||||
{
|
||||
label: '外部交易流水号',
|
||||
prop: 'external_transaction_no',
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{ label: '收款方式', prop: 'payment_method_name', formatter: (value) => value || '-' },
|
||||
{
|
||||
label: '付款金额',
|
||||
@@ -181,11 +181,6 @@
|
||||
prop: 'paid_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
},
|
||||
{
|
||||
label: '外部交易流水号',
|
||||
prop: 'external_transaction_no',
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
]
|
||||
|
||||
const columnOptions = [
|
||||
{ label: '申请编号', prop: 'id' },
|
||||
{ label: '外部交易流水号', prop: 'external_transaction_no' },
|
||||
{ label: '收款方式', prop: 'payment_method_name' },
|
||||
{ label: '付款金额', prop: 'paid_amount' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
@@ -149,9 +149,9 @@
|
||||
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '申请编号',
|
||||
minWidth: 140,
|
||||
prop: 'external_transaction_no',
|
||||
label: '外部交易流水号',
|
||||
minWidth: 220,
|
||||
formatter: (row: EmployeeCollectionApplication) =>
|
||||
h(
|
||||
'span',
|
||||
@@ -159,7 +159,7 @@
|
||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||
onClick: () => handleViewDetail(row)
|
||||
},
|
||||
`#${row.id}`
|
||||
row.external_transaction_no || '-'
|
||||
)
|
||||
},
|
||||
{
|
||||
|
||||
@@ -167,8 +167,8 @@
|
||||
status: undefined as number | undefined,
|
||||
customer_id: undefined as number | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_from: '',
|
||||
created_to: ''
|
||||
start_time: '',
|
||||
end_time: ''
|
||||
}
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
||||
@@ -258,8 +258,8 @@
|
||||
source_no: searchForm.source_no.trim() || undefined,
|
||||
status: searchForm.status,
|
||||
customer_id: searchForm.customer_id,
|
||||
created_from: searchForm.created_from || undefined,
|
||||
created_to: searchForm.created_to || undefined
|
||||
start_time: searchForm.start_time || undefined,
|
||||
end_time: searchForm.end_time || undefined
|
||||
})
|
||||
|
||||
const searchShops = async (query: string) => {
|
||||
@@ -391,11 +391,11 @@
|
||||
|
||||
const handleSearch = () => {
|
||||
if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) {
|
||||
searchForm.created_from = searchForm.dateRange[0]
|
||||
searchForm.created_to = searchForm.dateRange[1]
|
||||
searchForm.start_time = searchForm.dateRange[0]
|
||||
searchForm.end_time = searchForm.dateRange[1]
|
||||
} else {
|
||||
searchForm.created_from = ''
|
||||
searchForm.created_to = ''
|
||||
searchForm.start_time = ''
|
||||
searchForm.end_time = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
reload()
|
||||
|
||||
226
src/views/settings/h5-popup-configuration/detail.vue
Normal file
226
src/views/settings/h5-popup-configuration/detail.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div class="h5-popup-configuration-detail-page">
|
||||
<ElCard shadow="never">
|
||||
<div class="detail-header">
|
||||
<ElButton @click="handleBack">
|
||||
<template #icon>
|
||||
<ElIcon><ArrowLeft /></ElIcon>
|
||||
</template>
|
||||
返回
|
||||
</ElButton>
|
||||
<h2 class="detail-title">H5运营弹窗配置详情</h2>
|
||||
</div>
|
||||
|
||||
<DetailPage v-if="configuration" :sections="detailSections" :data="configuration" />
|
||||
|
||||
<div v-if="loading" class="loading-container">
|
||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
|
||||
<ElEmpty v-else-if="!configuration" description="未找到运营弹窗配置" />
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElButton, ElCard, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { H5PopupConfigurationService } from '@/api/modules'
|
||||
import type { H5PopupConfiguration, H5PopupPage, H5PopupType } from '@/types/api'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { getErrorMessage } from '@/utils/business'
|
||||
|
||||
defineOptions({ name: 'H5PopupConfigurationDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const configuration = ref<H5PopupConfiguration | null>(null)
|
||||
|
||||
const popupTypeNames: Record<H5PopupType, string> = {
|
||||
promotion: '套餐政策推广',
|
||||
announcement: '通用公告'
|
||||
}
|
||||
const pageNames: Record<H5PopupPage, string> = {
|
||||
home: '首页',
|
||||
asset_detail: '资产详情',
|
||||
package_purchase: '套餐购买',
|
||||
asset_wallet_recharge: '资产钱包充值'
|
||||
}
|
||||
const frequencyNames = {
|
||||
once: '每客户每配置版本仅一次',
|
||||
daily: '每客户每配置版本每个上海自然日一次'
|
||||
}
|
||||
const actionTypeNames: Record<string, string> = {
|
||||
package_purchase: '套餐购买',
|
||||
asset_wallet_recharge: '资产钱包充值'
|
||||
}
|
||||
const cardTypeNames: Record<string, string> = {
|
||||
CMCC: '中国移动',
|
||||
CUCC: '中国联通',
|
||||
CTCC: '中国电信',
|
||||
CBN: '中国广电'
|
||||
}
|
||||
|
||||
const formatList = (
|
||||
values: Array<string | number> | null | undefined,
|
||||
formatter?: (value: string) => string
|
||||
) => {
|
||||
if (!values?.length) return '全部'
|
||||
return values.map((value) => formatter?.(String(value)) || String(value)).join('、')
|
||||
}
|
||||
|
||||
const detailSections = computed((): DetailSection[] => [
|
||||
{
|
||||
title: '基础信息',
|
||||
fields: [
|
||||
{ label: '配置ID', prop: 'id' },
|
||||
{ label: '版本', prop: 'version' },
|
||||
{
|
||||
label: '弹窗类型',
|
||||
render: (data: H5PopupConfiguration) =>
|
||||
h(
|
||||
ElTag,
|
||||
{ type: 'primary' },
|
||||
() => data.popup_type_text || popupTypeNames[data.popup_type]
|
||||
)
|
||||
},
|
||||
{
|
||||
label: '启停状态',
|
||||
render: (data: H5PopupConfiguration) =>
|
||||
h(
|
||||
ElTag,
|
||||
{ type: data.enabled ? 'success' : 'info' },
|
||||
() => data.enabled_text || (data.enabled ? '启用' : '停用')
|
||||
)
|
||||
},
|
||||
{ label: '标题', prop: 'title', fullWidth: true },
|
||||
{ label: '正文', prop: 'content', fullWidth: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '投放配置',
|
||||
fields: [
|
||||
{
|
||||
label: '命中页面',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) =>
|
||||
formatList(data.pages, (value) => pageNames[value as H5PopupPage] || value),
|
||||
fullWidth: true
|
||||
},
|
||||
{ label: '优先级', prop: 'priority' },
|
||||
{
|
||||
label: '投放频率',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) =>
|
||||
data.frequency_text || frequencyNames[data.frequency] || data.frequency
|
||||
},
|
||||
{
|
||||
label: '受控动作',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) =>
|
||||
actionTypeNames[data.action_type || ''] || '无'
|
||||
},
|
||||
{
|
||||
label: '生效开始',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) => formatDateTime(data.starts_at)
|
||||
},
|
||||
{
|
||||
label: '生效结束',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) => formatDateTime(data.ends_at)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '投放范围与审计信息',
|
||||
fields: [
|
||||
{
|
||||
label: '店铺范围',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) => formatList(data.shop_ids),
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '设备类型范围',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) => formatList(data.device_types),
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '卡类型范围',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) =>
|
||||
formatList(data.card_types, (value) => cardTypeNames[value] || value),
|
||||
fullWidth: true
|
||||
},
|
||||
{ label: '创建人', prop: 'creator' },
|
||||
{
|
||||
label: '创建时间',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) =>
|
||||
formatDateTime(data.created_at)
|
||||
},
|
||||
{ label: '最近更新人', prop: 'updater' },
|
||||
{
|
||||
label: '最近更新时间',
|
||||
formatter: (_value: unknown, data: H5PopupConfiguration) =>
|
||||
formatDateTime(data.updated_at)
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
const loadDetail = async () => {
|
||||
const id = Number(route.params.id)
|
||||
if (!id) {
|
||||
ElMessage.error('无效的配置ID')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await H5PopupConfigurationService.getConfiguration(id)
|
||||
if (res.code === 0) configuration.value = res.data
|
||||
else ElMessage.error(res.msg || '获取运营弹窗配置详情失败')
|
||||
} catch (error) {
|
||||
ElMessage.error(getErrorMessage(error, '获取运营弹窗配置详情失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.push(RoutesAlias.H5PopupConfigurations)
|
||||
}
|
||||
|
||||
onMounted(() => void loadDetail())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.h5-popup-configuration-detail-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 240px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
@@ -52,6 +52,16 @@
|
||||
@closed="resetForm"
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="formRules" label-width="110px">
|
||||
<ElFormItem label="弹窗类型" prop="popup_type">
|
||||
<ElSelect v-model="form.popup_type" placeholder="请选择弹窗类型" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="item in POPUP_TYPE_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="弹窗标题" prop="title">
|
||||
<ElInput
|
||||
v-model="form.title"
|
||||
@@ -174,60 +184,12 @@
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 详情 -->
|
||||
<ElDialog v-model="detailDialogVisible" title="运营弹窗配置详情" width="680px">
|
||||
<div v-if="detailLoading" class="detail-loading">加载中...</div>
|
||||
<ElDescriptions v-else-if="detail" :column="2" border>
|
||||
<ElDescriptionsItem label="配置ID">{{ detail.id }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="版本">{{ detail.version }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="标题" :span="2">{{ detail.title }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="正文" :span="2">
|
||||
<span class="detail-content">{{ detail.content }}</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="命中页面" :span="2">
|
||||
{{ formatPages(detail.pages) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="店铺范围">
|
||||
{{ formatScope(detail.shop_ids) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备类型范围">
|
||||
{{ formatScope(detail.device_types) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="卡类型范围">
|
||||
{{ formatCardTypes(detail.card_types) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="优先级">{{ detail.priority }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="投放频率">
|
||||
{{ detail.frequency_text || FREQUENCY_NAMES[detail.frequency] || detail.frequency }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="启停状态">
|
||||
{{ detail.enabled_text || (detail.enabled ? '启用' : '停用') }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="受控动作">
|
||||
{{ ACTION_TYPE_NAMES[detail.action_type] || '无' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="生效开始" :span="2">
|
||||
{{ formatDateTime(detail.starts_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="生效结束" :span="2">
|
||||
{{ formatDateTime(detail.ends_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="创建人">{{ detail.creator }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="创建时间">{{
|
||||
formatDateTime(detail.created_at)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最近更新人">{{ detail.updater }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最近更新时间">{{
|
||||
formatDateTime(detail.updated_at)
|
||||
}}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElDialog>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox, ElTag, FormInstance, FormRules } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { H5PopupConfigurationService, ShopService } from '@/api/modules'
|
||||
@@ -242,12 +204,14 @@
|
||||
H5PopupCardType,
|
||||
H5PopupConfiguration,
|
||||
H5PopupFrequency,
|
||||
H5PopupPage
|
||||
H5PopupPage,
|
||||
H5PopupType
|
||||
} from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'H5PopupConfiguration' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const router = useRouter()
|
||||
const perms = AUGUST_PERMISSIONS.h5PopupConfiguration
|
||||
|
||||
const canCreate = computed(() => hasAuth(perms.create))
|
||||
@@ -267,6 +231,14 @@
|
||||
{ label: '套餐购买', value: 'package_purchase' },
|
||||
{ label: '资产钱包充值', value: 'asset_wallet_recharge' }
|
||||
]
|
||||
const POPUP_TYPE_NAMES: Record<H5PopupType, string> = {
|
||||
promotion: '套餐政策推广',
|
||||
announcement: '通用公告'
|
||||
}
|
||||
const POPUP_TYPE_OPTIONS: Array<{ label: string; value: H5PopupType }> = [
|
||||
{ label: '套餐政策推广', value: 'promotion' },
|
||||
{ label: '通用公告', value: 'announcement' }
|
||||
]
|
||||
const FREQUENCY_NAMES: Record<H5PopupFrequency, string> = {
|
||||
once: '每客户每配置版本仅一次',
|
||||
daily: '每客户每配置版本每个上海自然日一次'
|
||||
@@ -322,6 +294,7 @@
|
||||
]
|
||||
|
||||
const columnOptions = [
|
||||
{ label: '弹窗类型', prop: 'popup_type' },
|
||||
{ label: '标题', prop: 'title' },
|
||||
{ label: '命中页面', prop: 'pages' },
|
||||
{ label: '范围', prop: 'scope' },
|
||||
@@ -341,15 +314,6 @@
|
||||
return pages.map((page) => PAGE_NAMES[page] || page).join('、')
|
||||
}
|
||||
|
||||
const formatScope = (values: string[] | number[] | null | undefined): string => {
|
||||
if (!values || values.length === 0) return '全部'
|
||||
const text = values
|
||||
.slice(0, 3)
|
||||
.map((item) => (typeof item === 'number' && item !== 0 ? `#${item}` : String(item)))
|
||||
.join('、')
|
||||
return values.length > 3 ? `${text} 等${values.length}项` : text
|
||||
}
|
||||
|
||||
const formatCardTypes = (values: H5PopupCardType[] | null | undefined): string => {
|
||||
if (!values || values.length === 0) return '全部'
|
||||
return values.map((item) => CARD_TYPE_NAMES[item] || item).join('、')
|
||||
@@ -370,7 +334,31 @@
|
||||
}
|
||||
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{ prop: 'title', label: '标题', minWidth: 180, showOverflowTooltip: true },
|
||||
{
|
||||
prop: 'popup_type',
|
||||
label: '弹窗类型',
|
||||
width: 120,
|
||||
formatter: (row: H5PopupConfiguration) =>
|
||||
row.popup_type_text || POPUP_TYPE_NAMES[row.popup_type] || row.popup_type
|
||||
},
|
||||
{
|
||||
prop: 'title',
|
||||
label: '标题',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: H5PopupConfiguration) =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||
onClick: (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
goDetail(row)
|
||||
}
|
||||
},
|
||||
row.title
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'pages',
|
||||
label: '命中页面',
|
||||
@@ -409,7 +397,7 @@
|
||||
prop: 'action_type',
|
||||
label: '受控动作',
|
||||
width: 120,
|
||||
formatter: (row: H5PopupConfiguration) => ACTION_TYPE_NAMES[row.action_type] || '无'
|
||||
formatter: (row: H5PopupConfiguration) => ACTION_TYPE_NAMES[row.action_type || ''] || '无'
|
||||
},
|
||||
{
|
||||
prop: 'effective_time',
|
||||
@@ -500,10 +488,15 @@
|
||||
|
||||
// ========== 行操作 ==========
|
||||
|
||||
const goDetail = (row: H5PopupConfiguration) => {
|
||||
router.push({
|
||||
name: 'H5PopupConfigurationDetail',
|
||||
params: { id: String(row.id) }
|
||||
})
|
||||
}
|
||||
|
||||
const getActions = (row: H5PopupConfiguration) => {
|
||||
const actions: any[] = [
|
||||
{ label: '详情', handler: () => openDetailDialog(row), type: 'primary' }
|
||||
]
|
||||
const actions: any[] = []
|
||||
if (canUpdate.value) {
|
||||
actions.push({ label: '编辑', handler: () => openEditDialog(row) })
|
||||
}
|
||||
@@ -555,33 +548,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 详情 ==========
|
||||
|
||||
const detailDialogVisible = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detail = ref<H5PopupConfiguration | null>(null)
|
||||
|
||||
const openDetailDialog = async (row: H5PopupConfiguration) => {
|
||||
detail.value = null
|
||||
detailDialogVisible.value = true
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const res = await H5PopupConfigurationService.getConfiguration(row.id)
|
||||
if (res.code === 0 && res.data) {
|
||||
detail.value = res.data
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取配置详情失败')
|
||||
detailDialogVisible.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取运营弹窗配置详情失败:', error)
|
||||
ElMessage.error(normalizeApiError(error).message || '获取配置详情失败')
|
||||
detailDialogVisible.value = false
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 创建 / 编辑 ==========
|
||||
|
||||
const formDialogVisible = ref(false)
|
||||
@@ -593,19 +559,21 @@
|
||||
|
||||
const emptyForm = (): {
|
||||
id: number
|
||||
popup_type: H5PopupType
|
||||
title: string
|
||||
content: string
|
||||
pages: H5PopupPage[]
|
||||
priority: number
|
||||
frequency: H5PopupFrequency
|
||||
enabled: boolean
|
||||
action_type: H5PopupActionType
|
||||
action_type: H5PopupActionType | ''
|
||||
shop_ids: number[]
|
||||
device_types: string[]
|
||||
card_types: H5PopupCardType[]
|
||||
timeRange: string[]
|
||||
} => ({
|
||||
id: 0,
|
||||
popup_type: 'promotion',
|
||||
title: '',
|
||||
content: '',
|
||||
pages: [],
|
||||
@@ -672,6 +640,7 @@
|
||||
}
|
||||
|
||||
const formRules: FormRules = {
|
||||
popup_type: [{ required: true, message: '请选择弹窗类型', trigger: 'change' }],
|
||||
title: [
|
||||
{ required: true, message: '请输入弹窗标题', trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: '弹窗标题长度为 1~100 字符', trigger: 'blur' }
|
||||
@@ -720,13 +689,14 @@
|
||||
formMode.value = 'edit'
|
||||
Object.assign(form, {
|
||||
id: row.id,
|
||||
popup_type: row.popup_type,
|
||||
title: row.title,
|
||||
content: row.content,
|
||||
pages: [...(row.pages || [])],
|
||||
priority: row.priority,
|
||||
frequency: row.frequency,
|
||||
enabled: row.enabled,
|
||||
action_type: (row.action_type || '') as H5PopupActionType,
|
||||
action_type: row.action_type || '',
|
||||
shop_ids: [...(row.shop_ids || [])],
|
||||
device_types: [...(row.device_types || [])],
|
||||
card_types: [...(row.card_types || [])],
|
||||
@@ -741,6 +711,7 @@
|
||||
if (!(await formRef.value?.validate().catch(() => false))) return
|
||||
formLoading.value = true
|
||||
const payload = {
|
||||
popup_type: form.popup_type,
|
||||
title: form.title.trim(),
|
||||
content: form.content.trim(),
|
||||
pages: [...form.pages],
|
||||
@@ -749,14 +720,16 @@
|
||||
starts_at: form.timeRange[0],
|
||||
ends_at: form.timeRange[1],
|
||||
enabled: form.enabled,
|
||||
action_type: form.action_type,
|
||||
shop_ids: [...form.shop_ids],
|
||||
device_types: [...form.device_types],
|
||||
card_types: [...form.card_types]
|
||||
}
|
||||
try {
|
||||
if (formMode.value === 'create') {
|
||||
const res = await H5PopupConfigurationService.createConfiguration(payload)
|
||||
const res = await H5PopupConfigurationService.createConfiguration({
|
||||
...payload,
|
||||
action_type: form.action_type || null
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
ElMessage.success('创建成功')
|
||||
formDialogVisible.value = false
|
||||
@@ -767,7 +740,8 @@
|
||||
} else {
|
||||
const res = await H5PopupConfigurationService.updateConfiguration(form.id, {
|
||||
id: form.id,
|
||||
...payload
|
||||
...payload,
|
||||
action_type: form.action_type
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
ElMessage.success(
|
||||
@@ -796,15 +770,4 @@
|
||||
.h5-popup-configuration-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.detail-loading {
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,34 +1,6 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="business-owner-import-page" id="table-full-screen">
|
||||
<!-- 导入入口卡片 -->
|
||||
<ElCard shadow="never" class="import-entry-card">
|
||||
<div class="import-entry">
|
||||
<div class="import-tip">
|
||||
<p>通过 CSV 批量交接/清空店铺平台业务员。</p>
|
||||
<p
|
||||
>表头固定:<code>店铺编码,操作类型,业务员登录账号,备注</code>;操作类型仅支持「换绑」或「清空」。</p
|
||||
>
|
||||
<p>支持 UTF-8(可带 BOM),一行失败不影响其他行。</p>
|
||||
</div>
|
||||
<div class="import-actions">
|
||||
<ElButton
|
||||
v-permission="SHOP_BUSINESS_OWNER_PERMISSIONS.importPage"
|
||||
@click="handleDownloadTemplate"
|
||||
>
|
||||
下载模板
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
v-permission="SHOP_BUSINESS_OWNER_PERMISSIONS.importCreate"
|
||||
@click="importDialogVisible = true"
|
||||
>
|
||||
导入业务员
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
@@ -37,20 +9,29 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElSelect
|
||||
v-model="searchForm.status"
|
||||
placeholder="状态:全部"
|
||||
clearable
|
||||
style="width: 160px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<ElOption
|
||||
v-for="option in importStatusOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
<div class="header-actions">
|
||||
<ElSelect
|
||||
v-model="searchForm.status"
|
||||
placeholder="状态:全部"
|
||||
clearable
|
||||
style="width: 160px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<ElOption
|
||||
v-for="option in importStatusOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
<ElButton
|
||||
type="primary"
|
||||
v-permission="SHOP_BUSINESS_OWNER_PERMISSIONS.importCreate"
|
||||
@click="importDialogVisible = true"
|
||||
>
|
||||
导入业务员
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -63,8 +44,6 @@
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:actions="getActions"
|
||||
:actionsWidth="120"
|
||||
:marginTop="10"
|
||||
:height="400"
|
||||
@size-change="handleSizeChange"
|
||||
@@ -80,7 +59,12 @@
|
||||
<!-- 新建导入弹窗 -->
|
||||
<ElDialog v-model="importDialogVisible" title="导入业务员" width="560px" destroy-on-close>
|
||||
<ElAlert type="info" :closable="false" show-icon class="import-alert">
|
||||
请先下载模板,按模板填写店铺编码与操作类型后再上传 CSV。
|
||||
<template #title>导入说明</template>
|
||||
<div>通过 CSV 批量交接或清空店铺平台业务员。</div>
|
||||
<div>
|
||||
表头固定:<code>店铺编码,操作类型,业务员登录账号,备注</code>;操作类型仅支持「换绑」或「清空」。
|
||||
</div>
|
||||
<div>支持 UTF-8(可带 BOM),一行失败不影响其他行。</div>
|
||||
</ElAlert>
|
||||
<div class="upload-tip-row">
|
||||
<ElButton @click="handleDownloadTemplate">下载模板</ElButton>
|
||||
@@ -118,11 +102,11 @@
|
||||
<!-- 导入结果抽屉 -->
|
||||
<ElDrawer
|
||||
v-model="detailDrawerVisible"
|
||||
:title="`导入结果 - ${currentTask?.id || ''}`"
|
||||
size="70%"
|
||||
:title="`导入结果${currentTask?.task_no ? ` - ${currentTask.task_no}` : ''}`"
|
||||
size="820px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="currentTask" class="import-detail">
|
||||
<div v-if="currentTask" v-loading="detailLoading" class="import-detail">
|
||||
<ElAlert
|
||||
:type="detailAlertType"
|
||||
:closable="false"
|
||||
@@ -136,19 +120,19 @@
|
||||
</ElAlert>
|
||||
|
||||
<div class="detail-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-item stat-item--total">
|
||||
<span class="stat-label">总数</span>
|
||||
<strong>{{ currentTask.total_count ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-item stat-item--success">
|
||||
<span class="stat-label">成功</span>
|
||||
<strong class="stat-success">{{ currentTask.success_count ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-item stat-item--fail">
|
||||
<span class="stat-label">失败</span>
|
||||
<strong class="stat-fail">{{ currentTask.fail_count ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-item stat-item--status">
|
||||
<span class="stat-label">状态</span>
|
||||
<strong>{{ currentTask.status_name || importStatusLabel(currentTask.status) }}</strong>
|
||||
</div>
|
||||
@@ -181,6 +165,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section-title">
|
||||
<span>逐行处理结果</span>
|
||||
<ElTag size="small" type="info">{{ rowItems.length }} 条</ElTag>
|
||||
</div>
|
||||
<ElTable v-if="rowItems.length" :data="rowItems" border stripe max-height="420">
|
||||
<ElTableColumn label="行号" prop="line" width="80" align="center" />
|
||||
<ElTableColumn label="店铺编码" prop="shop_code" min-width="140" />
|
||||
@@ -195,8 +183,8 @@
|
||||
<ElTableColumn label="失败原因" prop="reason" min-width="220" show-overflow-tooltip />
|
||||
</ElTable>
|
||||
<ElEmpty
|
||||
v-else-if="!detailLoading && !rowItems.length && currentTask.status === 4"
|
||||
description="任务级失败,无行级明细"
|
||||
v-else-if="!detailLoading"
|
||||
:description="currentTask.status === 4 ? '任务级失败,无行级明细' : '暂无逐行处理结果'"
|
||||
/>
|
||||
</div>
|
||||
</ElDrawer>
|
||||
@@ -211,14 +199,11 @@
|
||||
import { ShopService, StorageService } from '@/api/modules'
|
||||
import type { BusinessOwnerImportDetail } from '@/types/api'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { SHOP_BUSINESS_OWNER_PERMISSIONS } from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'BusinessOwnerImport' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
const importStatusOptions = [
|
||||
{ label: '待处理', value: 1 },
|
||||
{ label: '处理中', value: 2 },
|
||||
@@ -244,7 +229,6 @@
|
||||
})
|
||||
|
||||
const columnOptions = [
|
||||
{ label: '任务ID', prop: 'id', width: 90 },
|
||||
{ label: '任务编号', prop: 'task_no', width: 160 },
|
||||
{ label: '文件名', prop: 'file_name', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: '创建人', prop: 'creator_name', width: 100 },
|
||||
@@ -258,16 +242,23 @@
|
||||
]
|
||||
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '任务ID',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
prop: 'task_no',
|
||||
label: '任务编号',
|
||||
width: 160,
|
||||
showOverflowTooltip: true
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: BusinessOwnerImportDetail) =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||
onClick: (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
openDetail(row)
|
||||
}
|
||||
},
|
||||
row.task_no || '--'
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'file_name',
|
||||
@@ -318,17 +309,6 @@
|
||||
}
|
||||
])
|
||||
|
||||
const getActions = (row: BusinessOwnerImportDetail) => {
|
||||
if (!hasAuth(SHOP_BUSINESS_OWNER_PERMISSIONS.importPage)) return []
|
||||
return [
|
||||
{
|
||||
label: '查看结果',
|
||||
handler: () => openDetail(row),
|
||||
type: 'primary' as const
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -337,8 +317,13 @@
|
||||
page_size: pagination.pageSize,
|
||||
status: searchForm.status
|
||||
})
|
||||
tableData.value = res.data?.items || []
|
||||
pagination.total = res.data?.total || 0
|
||||
if (res.code === 0 && res.data) {
|
||||
const { items, total, page, size } = res.data
|
||||
tableData.value = items || []
|
||||
pagination.total = total || 0
|
||||
pagination.page = page ?? pagination.page
|
||||
pagination.pageSize = size ?? pagination.pageSize
|
||||
}
|
||||
} catch {
|
||||
// 错误提示由请求层统一处理
|
||||
} finally {
|
||||
@@ -537,38 +522,10 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.import-entry-card {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.import-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.import-tip {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.8;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.import-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.import-alert {
|
||||
@@ -596,8 +553,11 @@
|
||||
}
|
||||
|
||||
.import-detail {
|
||||
padding: 0 4px 20px;
|
||||
|
||||
.detail-alert {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.detail-error {
|
||||
@@ -607,14 +567,35 @@
|
||||
}
|
||||
|
||||
.detail-stats {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-bottom: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
padding: 14px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 10px;
|
||||
|
||||
&--success {
|
||||
background: var(--el-color-success-light-9);
|
||||
border-color: var(--el-color-success-light-7);
|
||||
}
|
||||
|
||||
&--fail {
|
||||
background: var(--el-color-danger-light-9);
|
||||
border-color: var(--el-color-danger-light-7);
|
||||
}
|
||||
|
||||
&--status {
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-7);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
@@ -622,7 +603,8 @@
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 18px;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-success {
|
||||
@@ -638,11 +620,12 @@
|
||||
.detail-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 12px 24px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
gap: 14px 24px;
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 10px;
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
@@ -662,5 +645,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user