This commit is contained in:
@@ -104,20 +104,15 @@
|
||||
<!-- 只有非代理账号才显示归属店铺选择 -->
|
||||
<ElCol :span="12" v-if="!isAgentAccount">
|
||||
<ElFormItem label="归属店铺" prop="owner_shop_id">
|
||||
<ElTreeSelect
|
||||
<ElCascader
|
||||
v-model="form.owner_shop_id"
|
||||
:data="shopTreeData"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择归属店铺"
|
||||
filterable
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
:props="{
|
||||
label: 'shop_name',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
}"
|
||||
style="width: 100%"
|
||||
@change="handleShopChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -240,6 +235,37 @@
|
||||
const currentEnterpriseId = ref<number>(0)
|
||||
const shopTreeData = ref<ShopResponse[]>([])
|
||||
|
||||
// 店铺级联选择相关
|
||||
const shopCascadeOptions = ref<any[]>([])
|
||||
const shopCascadeProps = {
|
||||
lazy: true,
|
||||
checkStrictly: true,
|
||||
lazyLoad: async (node: any, resolve: any) => {
|
||||
const { level, value } = node
|
||||
const parentId = level === 0 ? undefined : value
|
||||
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
||||
if (res.code === 0) {
|
||||
const nodes = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
resolve(nodes)
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺级联数据失败:', error)
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
enterprise_name: '',
|
||||
@@ -510,19 +536,17 @@
|
||||
}
|
||||
})
|
||||
|
||||
// 加载店铺列表(获取所有店铺构建树形结构)
|
||||
// 加载店铺列表 - 改用级联查询
|
||||
const loadShopList = async () => {
|
||||
shopLoading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 9999 // 获取所有数据用于构建树形结构
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||
if (res.code === 0) {
|
||||
const items = res.data.items || []
|
||||
// 构建树形数据
|
||||
shopTreeData.value = buildTreeData(items)
|
||||
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取店铺列表失败:', error)
|
||||
@@ -531,6 +555,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 处理店铺选择变化
|
||||
const handleShopChange = (value: any) => {
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
form.owner_shop_id = value[value.length - 1]
|
||||
} else {
|
||||
form.owner_shop_id = null
|
||||
}
|
||||
}
|
||||
|
||||
// 构建树形数据
|
||||
const buildTreeData = (items: ShopResponse[]) => {
|
||||
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -76,7 +76,7 @@
|
||||
<ElFormItem label="已选设备数">
|
||||
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> 台
|
||||
</ElFormItem>
|
||||
<ElFormItem label="目标店铺" prop="target_shop_id">
|
||||
<ElFormItem label="店铺" prop="target_shop_id">
|
||||
<ElTreeSelect
|
||||
v-model="allocateForm.target_shop_id"
|
||||
:data="shopTreeData"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ElCard shadow="never" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('enterpriseDevices.title') }}</span>
|
||||
<span>企业设备列表</span>
|
||||
<ElButton @click="goBack">{{ $t('common.cancel') }}</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,7 +42,7 @@
|
||||
@click="showAllocateDialog"
|
||||
v-permission="'enterprise_device:allocate'"
|
||||
>
|
||||
{{ $t('enterpriseDevices.buttons.allocateDevices') }}
|
||||
授权设备
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="warning"
|
||||
@@ -50,7 +50,7 @@
|
||||
@click="showRecallDialog"
|
||||
v-permission="'enterprise_device:recall'"
|
||||
>
|
||||
{{ $t('enterpriseDevices.buttons.recallDevices') }}
|
||||
撤销授权
|
||||
</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
@@ -78,7 +78,7 @@
|
||||
<!-- 授权设备对话框 -->
|
||||
<ElDialog
|
||||
v-model="allocateDialogVisible"
|
||||
:title="$t('enterpriseDevices.dialog.allocateTitle')"
|
||||
title="授权设备给企业"
|
||||
width="75%"
|
||||
@close="handleAllocateDialogClose"
|
||||
>
|
||||
@@ -137,19 +137,13 @@
|
||||
<!-- 撤销授权对话框 -->
|
||||
<ElDialog
|
||||
v-model="recallDialogVisible"
|
||||
:title="$t('enterpriseDevices.dialog.recallTitle')"
|
||||
title="撤销设备授权"
|
||||
width="600px"
|
||||
@close="handleRecallDialogClose"
|
||||
>
|
||||
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules">
|
||||
<ElFormItem :label="$t('enterpriseDevices.form.selectedDevices')">
|
||||
<div>
|
||||
{{
|
||||
$t('enterpriseDevices.form.selectedCount', {
|
||||
count: selectedDevices.length
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<ElFormItem label="已选择设备">
|
||||
<div>已选择 {{ selectedDevices.length }} 个设备</div>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
@@ -165,14 +159,14 @@
|
||||
<!-- 结果对话框 -->
|
||||
<ElDialog
|
||||
v-model="resultDialogVisible"
|
||||
:title="$t('enterpriseDevices.dialog.resultTitle')"
|
||||
title="操作结果"
|
||||
width="700px"
|
||||
>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem :label="$t('enterpriseDevices.result.successCount')">
|
||||
<ElDescriptionsItem label="成功数量">
|
||||
<ElTag type="success">{{ operationResult.success_count }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem :label="$t('enterpriseDevices.result.failCount')">
|
||||
<ElDescriptionsItem label="失败数量">
|
||||
<ElTag type="danger">{{ operationResult.fail_count }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
@@ -182,16 +176,14 @@
|
||||
v-if="operationResult.failed_items && operationResult.failed_items.length > 0"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<ElDivider content-position="left">{{
|
||||
$t('enterpriseDevices.result.failedItems')
|
||||
}}</ElDivider>
|
||||
<ElDivider content-position="left">失败项</ElDivider>
|
||||
<ElTable :data="operationResult.failed_items" border max-height="300">
|
||||
<ElTableColumn
|
||||
prop="virtual_no"
|
||||
:label="$t('enterpriseDevices.result.deviceNo')"
|
||||
label="设备号"
|
||||
width="180"
|
||||
/>
|
||||
<ElTableColumn prop="reason" :label="$t('enterpriseDevices.result.reason')" />
|
||||
<ElTableColumn prop="reason" label="失败原因" />
|
||||
</ElTable>
|
||||
</div>
|
||||
|
||||
@@ -202,21 +194,19 @@
|
||||
"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<ElDivider content-position="left">{{
|
||||
$t('enterpriseDevices.result.authorizedDevices')
|
||||
}}</ElDivider>
|
||||
<ElDivider content-position="left">已授权设备</ElDivider>
|
||||
<ElTable :data="operationResult.authorized_devices" border max-height="200">
|
||||
<ElTableColumn
|
||||
prop="virtual_no"
|
||||
:label="$t('enterpriseDevices.result.deviceNo')"
|
||||
label="设备号"
|
||||
width="180"
|
||||
/>
|
||||
<ElTableColumn
|
||||
prop="device_id"
|
||||
:label="$t('enterpriseDevices.result.deviceId')"
|
||||
label="设备ID"
|
||||
width="100"
|
||||
/>
|
||||
<ElTableColumn :label="$t('enterpriseDevices.result.cardCount')">
|
||||
<ElTableColumn label="绑定卡数">
|
||||
<template #default="{ row }">
|
||||
{{ row.card_count || 0 }}
|
||||
</template>
|
||||
@@ -259,7 +249,7 @@
|
||||
|
||||
defineOptions({ name: 'EnterpriseDevices' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n() // 仅保留用于 common.confirm, common.cancel
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
@@ -326,12 +316,12 @@
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: t('enterpriseDevices.searchForm.deviceNo'),
|
||||
label: '设备号',
|
||||
prop: 'virtual_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: t('enterpriseDevices.searchForm.deviceNoPlaceholder')
|
||||
placeholder: '请输入设备号'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -375,12 +365,12 @@
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: t('enterpriseDevices.table.deviceId'), prop: 'device_id' },
|
||||
{ label: t('enterpriseDevices.table.deviceNo'), prop: 'virtual_no' },
|
||||
{ label: t('enterpriseDevices.table.deviceName'), prop: 'device_name' },
|
||||
{ label: t('enterpriseDevices.table.deviceModel'), prop: 'device_model' },
|
||||
{ label: t('enterpriseDevices.table.cardCount'), prop: 'card_count' },
|
||||
{ label: t('enterpriseDevices.table.authorizedAt'), prop: 'authorized_at' }
|
||||
{ label: '设备ID', prop: 'device_id' },
|
||||
{ label: '设备号', prop: 'virtual_no' },
|
||||
{ label: '设备名称', prop: 'device_name' },
|
||||
{ label: '设备型号', prop: 'device_model' },
|
||||
{ label: '绑定卡数量', prop: 'card_count' },
|
||||
{ label: '授权时间', prop: 'authorized_at' }
|
||||
]
|
||||
|
||||
const deviceList = ref<EnterpriseDeviceItem[]>([])
|
||||
@@ -421,32 +411,32 @@
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'device_id',
|
||||
label: t('enterpriseDevices.table.deviceId'),
|
||||
label: '设备ID',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: t('enterpriseDevices.table.deviceNo'),
|
||||
label: '设备号',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'device_name',
|
||||
label: t('enterpriseDevices.table.deviceName'),
|
||||
label: '设备名称',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'device_model',
|
||||
label: t('enterpriseDevices.table.deviceModel'),
|
||||
label: '设备型号',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'card_count',
|
||||
label: t('enterpriseDevices.table.cardCount'),
|
||||
label: '绑定卡数量',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'authorized_at',
|
||||
label: t('enterpriseDevices.table.authorizedAt'),
|
||||
label: '授权时间',
|
||||
width: 180,
|
||||
formatter: (row: EnterpriseDeviceItem) =>
|
||||
row.authorized_at ? formatDateTime(row.authorized_at) : '-'
|
||||
@@ -499,7 +489,7 @@
|
||||
getEnterpriseInfo()
|
||||
getTableData()
|
||||
} else {
|
||||
ElMessage.error(t('enterpriseDevices.messages.loadFailed'))
|
||||
ElMessage.error('加载设备列表失败')
|
||||
goBack()
|
||||
}
|
||||
})
|
||||
@@ -686,16 +676,13 @@
|
||||
|
||||
// 显示成功消息
|
||||
if (res.data.success_count > 0 && res.data.fail_count === 0) {
|
||||
ElMessage.success(t('enterpriseDevices.messages.allocateSuccess'))
|
||||
ElMessage.success('设备授权成功')
|
||||
} else if (res.data.success_count > 0 && res.data.fail_count > 0) {
|
||||
ElMessage.warning(
|
||||
t('enterpriseDevices.messages.allocatePartialSuccess', {
|
||||
success: res.data.success_count,
|
||||
fail: res.data.fail_count
|
||||
})
|
||||
`部分设备授权成功:成功 ${res.data.success_count} 个,失败 ${res.data.fail_count} 个`
|
||||
)
|
||||
} else {
|
||||
ElMessage.error(t('enterpriseDevices.messages.allocateFailed'))
|
||||
ElMessage.error('设备授权失败')
|
||||
}
|
||||
|
||||
getTableData()
|
||||
@@ -715,7 +702,7 @@
|
||||
// 显示撤销授权对话框
|
||||
const showRecallDialog = () => {
|
||||
if (selectedDevices.value.length === 0) {
|
||||
ElMessage.warning(t('enterpriseDevices.messages.noSelection'))
|
||||
ElMessage.warning('请先选择要撤销的设备')
|
||||
return
|
||||
}
|
||||
recallDialogVisible.value = true
|
||||
@@ -729,10 +716,8 @@
|
||||
if (!recallFormRef.value) return
|
||||
|
||||
ElMessageBox.confirm(
|
||||
t('enterpriseDevices.messages.recallConfirmText', {
|
||||
count: selectedDevices.value.length
|
||||
}),
|
||||
t('enterpriseDevices.messages.recallConfirm'),
|
||||
`确定要撤销选中的 ${selectedDevices.value.length} 个设备的授权吗?`,
|
||||
'撤销授权确认',
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
@@ -753,16 +738,13 @@
|
||||
|
||||
// 显示成功消息
|
||||
if (res.data.success_count > 0 && res.data.fail_count === 0) {
|
||||
ElMessage.success(t('enterpriseDevices.messages.recallSuccess'))
|
||||
ElMessage.success('撤销授权成功')
|
||||
} else if (res.data.success_count > 0 && res.data.fail_count > 0) {
|
||||
ElMessage.warning(
|
||||
t('enterpriseDevices.messages.recallPartialSuccess', {
|
||||
success: res.data.success_count,
|
||||
fail: res.data.fail_count
|
||||
})
|
||||
`部分设备撤销成功:成功 ${res.data.success_count} 个,失败 ${res.data.fail_count} 个`
|
||||
)
|
||||
} else {
|
||||
ElMessage.error(t('enterpriseDevices.messages.recallFailed'))
|
||||
ElMessage.error('撤销授权失败')
|
||||
}
|
||||
|
||||
// 清空选择
|
||||
@@ -774,7 +756,7 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
ElMessage.error(t('enterpriseDevices.messages.recallFailed'))
|
||||
ElMessage.error('撤销授权失败')
|
||||
} finally {
|
||||
recallLoading.value = false
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
:rules="allocateRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<ElFormItem label="目标店铺" prop="to_shop_id">
|
||||
<ElFormItem label="店铺" prop="to_shop_id">
|
||||
<ElSelect
|
||||
v-model="allocateForm.to_shop_id"
|
||||
placeholder="请选择或搜索目标店铺"
|
||||
@@ -669,6 +669,7 @@
|
||||
iccid: string
|
||||
msisdn: string
|
||||
virtual_no: string
|
||||
device_virtual_no: string
|
||||
is_distributed: undefined | number
|
||||
[key: string]: any
|
||||
} = {
|
||||
@@ -677,6 +678,7 @@
|
||||
iccid: '',
|
||||
msisdn: '',
|
||||
virtual_no: '',
|
||||
device_virtual_no: '',
|
||||
is_distributed: undefined
|
||||
}
|
||||
|
||||
@@ -827,12 +829,21 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '虚拟号',
|
||||
label: '卡虚拟号',
|
||||
prop: 'virtual_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入虚拟号'
|
||||
placeholder: '请输入卡虚拟号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '绑定设备虚拟号',
|
||||
prop: 'device_virtual_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入绑定设备虚拟号'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -854,7 +865,8 @@
|
||||
const columnOptions = [
|
||||
{ label: 'ICCID', prop: 'iccid' },
|
||||
{ label: '卡接入号', prop: 'msisdn' },
|
||||
{ label: '虚拟号', prop: 'virtual_no' },
|
||||
{ label: '卡虚拟号', prop: 'virtual_no' },
|
||||
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
|
||||
{ label: '卡业务类型', prop: 'card_category' },
|
||||
{ label: '运营商', prop: 'carrier_name' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
@@ -987,9 +999,15 @@
|
||||
},
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '虚拟号',
|
||||
label: '卡虚拟号',
|
||||
width: 160,
|
||||
formatter: (row: StandaloneIotCard) => row.virtual_no
|
||||
formatter: (row: StandaloneIotCard) => row.virtual_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_virtual_no',
|
||||
label: '绑定设备虚拟号',
|
||||
width: 160,
|
||||
formatter: (row: StandaloneIotCard) => row.device_virtual_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'card_category',
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<ElDialog v-model="importDialogVisible" title="批量导入设备" width="700px" align-center>
|
||||
<ElDialog v-model="importDialogVisible" title="批量导入设备" width="40%" align-center>
|
||||
<ElAlert type="info" :closable="false" style="margin-bottom: 20px">
|
||||
<template #title>
|
||||
<div style="line-height: 1.8">
|
||||
@@ -62,7 +62,7 @@
|
||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||
<p
|
||||
>4.
|
||||
必填列:virtual_no(设备号)、device_name(设备名称)、device_model(设备型号)、device_type(设备类型)、imei(设备IMEI号)</p
|
||||
必填列:virtual_no(虚拟号)、device_name(设备名称)、device_model(设备型号)、device_type(设备类型)、imei(设备IMEI号)</p
|
||||
>
|
||||
<p
|
||||
>5. 可选列:manufacturer(制造商)、max_sim_slots(最大插槽数,默认4)、iccid_1 ~
|
||||
|
||||
@@ -10,22 +10,22 @@
|
||||
</i>
|
||||
</div>
|
||||
<ElDropdown @command="changeLanguage" popper-class="langDropDownStyle">
|
||||
<div class="btn language-btn">
|
||||
<i class="iconfont-sys icon-language"></i>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">
|
||||
<ElDropdownItem
|
||||
:command="lang.value"
|
||||
:class="{ 'is-selected': locale === lang.value }"
|
||||
>
|
||||
<span class="menu-txt">{{ lang.label }}</span>
|
||||
<i v-if="locale === lang.value" class="iconfont-sys icon-check"></i>
|
||||
</ElDropdownItem>
|
||||
</div>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
<!--<div class="btn language-btn">-->
|
||||
<!-- <i class="iconfont-sys icon-language"></i>-->
|
||||
<!--</div>-->
|
||||
<!--<template #dropdown>-->
|
||||
<!-- <ElDropdownMenu>-->
|
||||
<!-- <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">-->
|
||||
<!-- <ElDropdownItem-->
|
||||
<!-- :command="lang.value"-->
|
||||
<!-- :class="{ 'is-selected': locale === lang.value }"-->
|
||||
<!-- >-->
|
||||
<!-- <span class="menu-txt">{{ lang.label }}</span>-->
|
||||
<!-- <i v-if="locale === lang.value" class="iconfont-sys icon-check"></i>-->
|
||||
<!-- </ElDropdownItem>-->
|
||||
<!-- </div>-->
|
||||
<!-- </ElDropdownMenu>-->
|
||||
<!--</template>-->
|
||||
</ElDropdown>
|
||||
</div>
|
||||
<div class="header">
|
||||
@@ -97,9 +97,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppConfig from '@/config'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { getCssVar } from '@/utils/ui'
|
||||
import { languageOptions } from '@/locales'
|
||||
import { LanguageEnum, SystemThemeEnum } from '@/enums/appEnum'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
|
||||
@@ -40,24 +40,24 @@
|
||||
<!-- 拒绝提现对话框 -->
|
||||
<ElDialog
|
||||
v-model="rejectDialogVisible"
|
||||
:title="$t('commission.dialog.reject')"
|
||||
title="拒绝提现申请"
|
||||
width="500px"
|
||||
>
|
||||
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
|
||||
<ElFormItem :label="$t('commission.form.rejectReason')" prop="reject_reason">
|
||||
<ElFormItem label="拒绝原因" prop="reject_reason">
|
||||
<ElInput
|
||||
v-model="rejectForm.reject_reason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="$t('commission.form.rejectReasonPlaceholder')"
|
||||
placeholder="请输入拒绝原因"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('commission.form.remark')" prop="remark">
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput
|
||||
v-model="rejectForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="$t('commission.form.remarkPlaceholder')"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
defineOptions({ name: 'WithdrawalApproval' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n() // 仅保留用于 common.tips, common.confirm, common.cancel
|
||||
|
||||
const rejectDialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -116,50 +116,50 @@
|
||||
|
||||
// 提现状态选项
|
||||
const withdrawalStatusOptions = [
|
||||
{ label: t('commission.status.pending'), value: 1 },
|
||||
{ label: t('commission.status.approved'), value: 2 },
|
||||
{ label: t('commission.status.rejected'), value: 3 },
|
||||
{ label: t('commission.status.completed'), value: 4 }
|
||||
{ label: '待审核', value: 1 },
|
||||
{ label: '已通过', value: 2 },
|
||||
{ label: '已拒绝', value: 3 },
|
||||
{ label: '已到账', value: 4 }
|
||||
]
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: t('commission.searchForm.status'),
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: withdrawalStatusOptions,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: t('commission.searchForm.statusPlaceholder')
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('commission.searchForm.withdrawalNo'),
|
||||
label: '提现单号',
|
||||
prop: 'withdrawal_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: t('commission.searchForm.withdrawalNoPlaceholder')
|
||||
placeholder: '请输入提现单号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('commission.searchForm.shopName'),
|
||||
label: '店铺名称',
|
||||
prop: 'shop_name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: t('commission.searchForm.shopNamePlaceholder')
|
||||
placeholder: '请输入店铺名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('commission.searchForm.dateRange'),
|
||||
label: '申请时间',
|
||||
prop: 'dateRange',
|
||||
type: 'daterange',
|
||||
config: {
|
||||
clearable: true,
|
||||
startPlaceholder: t('commission.searchForm.dateRangePlaceholder.0'),
|
||||
endPlaceholder: t('commission.searchForm.dateRangePlaceholder.1'),
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
onChange: (val: [string, string] | null) => {
|
||||
if (val && val.length === 2) {
|
||||
@@ -183,27 +183,27 @@
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: t('commission.table.withdrawalNo'), prop: 'withdrawal_no' },
|
||||
{ label: t('commission.table.shopName'), prop: 'shop_name' },
|
||||
{ label: t('commission.table.applicant'), prop: 'applicant_name' },
|
||||
{ label: t('commission.table.withdrawalAmount'), prop: 'amount' },
|
||||
{ label: t('commission.table.fee'), prop: 'fee' },
|
||||
{ label: t('commission.table.actualAmount'), prop: 'actual_amount' },
|
||||
{ label: t('commission.table.withdrawalMethod'), prop: 'withdrawal_method' },
|
||||
{ label: t('commission.table.status'), prop: 'status' },
|
||||
{ label: t('commission.table.applyTime'), prop: 'created_at' },
|
||||
{ label: t('commission.table.approveTime'), prop: 'processed_at' },
|
||||
{ label: t('commission.table.actions'), prop: 'operation' }
|
||||
{ label: '提现单号', prop: 'withdrawal_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '申请人', prop: 'applicant_name' },
|
||||
{ label: '提现金额', prop: 'amount' },
|
||||
{ label: '手续费', prop: 'fee' },
|
||||
{ label: '实际到账', prop: 'actual_amount' },
|
||||
{ label: '提现方式', prop: 'withdrawal_method' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '申请时间', prop: 'created_at' },
|
||||
{ label: '审批时间', prop: 'processed_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
|
||||
const rejectRules = reactive<FormRules>({
|
||||
reject_reason: [
|
||||
{ required: true, message: t('commission.validation.rejectReasonRequired'), trigger: 'blur' }
|
||||
{ required: true, message: '请输入拒绝原因', trigger: 'blur' }
|
||||
],
|
||||
remark: [
|
||||
{ required: true, message: t('commission.validation.remarkRequired'), trigger: 'blur' }
|
||||
{ required: true, message: '请输入备注', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
@@ -218,43 +218,43 @@
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'withdrawal_no',
|
||||
label: t('commission.table.withdrawalNo'),
|
||||
label: '提现单号',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: t('commission.table.shopName'),
|
||||
label: '店铺名称',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'applicant_name',
|
||||
label: t('commission.table.applicant'),
|
||||
label: '申请人',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'amount',
|
||||
label: t('commission.table.withdrawalAmount'),
|
||||
label: '提现金额',
|
||||
width: 120,
|
||||
align: 'right',
|
||||
formatter: (row: WithdrawalRequestItem) => formatMoney(row.amount)
|
||||
},
|
||||
{
|
||||
prop: 'fee',
|
||||
label: t('commission.table.fee'),
|
||||
label: '手续费',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
formatter: (row: WithdrawalRequestItem) => formatMoney(row.fee)
|
||||
},
|
||||
{
|
||||
prop: 'actual_amount',
|
||||
label: t('commission.table.actualAmount'),
|
||||
label: '实际到账',
|
||||
width: 120,
|
||||
align: 'right',
|
||||
formatter: (row: WithdrawalRequestItem) => formatMoney(row.actual_amount)
|
||||
},
|
||||
{
|
||||
prop: 'withdrawal_method',
|
||||
label: t('commission.table.withdrawalMethod'),
|
||||
label: '提现方式',
|
||||
width: 120,
|
||||
formatter: (row: WithdrawalRequestItem) => {
|
||||
const method = WithdrawalMethodMap[row.withdrawal_method as WithdrawalMethod]
|
||||
@@ -263,7 +263,7 @@
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: t('commission.table.status'),
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: WithdrawalRequestItem) => {
|
||||
const statusInfo = WithdrawalStatusMap[row.status as keyof typeof WithdrawalStatusMap]
|
||||
@@ -272,19 +272,19 @@
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: t('commission.table.applyTime'),
|
||||
label: '申请时间',
|
||||
width: 180,
|
||||
formatter: (row: WithdrawalRequestItem) => formatDateTime(row.created_at)
|
||||
},
|
||||
{
|
||||
prop: 'processed_at',
|
||||
label: t('commission.table.approveTime'),
|
||||
label: '审批时间',
|
||||
width: 180,
|
||||
formatter: (row: WithdrawalRequestItem) => formatDateTime(row.processed_at)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: t('commission.table.actions'),
|
||||
label: '操作',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
formatter: (row: WithdrawalRequestItem) => {
|
||||
@@ -292,12 +292,12 @@
|
||||
if (row.status === 1) {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
text: t('commission.buttons.approve'),
|
||||
text: '审批通过',
|
||||
iconColor: '#67C23A',
|
||||
onClick: () => handleApprove(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
text: t('commission.buttons.reject'),
|
||||
text: '拒绝',
|
||||
iconColor: '#F56C6C',
|
||||
onClick: () => showRejectDialog(row)
|
||||
})
|
||||
@@ -368,7 +368,7 @@
|
||||
|
||||
// 审批通过
|
||||
const handleApprove = (row: WithdrawalRequestItem) => {
|
||||
ElMessageBox.confirm(t('commission.messages.approveConfirm'), t('common.tips'), {
|
||||
ElMessageBox.confirm('确定要通过该提现申请吗?', t('common.tips'), {
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
@@ -376,7 +376,7 @@
|
||||
.then(async () => {
|
||||
try {
|
||||
await CommissionService.approveWithdrawal(row.id)
|
||||
ElMessage.success(t('commission.messages.approveSuccess'))
|
||||
ElMessage.success('审批通过成功')
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -407,7 +407,7 @@
|
||||
reject_reason: rejectForm.reject_reason,
|
||||
remark: rejectForm.remark
|
||||
})
|
||||
ElMessage.success(t('commission.messages.rejectSuccess'))
|
||||
ElMessage.success('拒绝成功')
|
||||
rejectDialogVisible.value = false
|
||||
rejectFormRef.value?.resetFields()
|
||||
getTableData()
|
||||
|
||||
@@ -18,7 +18,12 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="showCreateDialog" v-if="hasAuth('agent_recharge:create')">创建充值订单</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="showCreateDialog"
|
||||
v-if="hasAuth('agent_recharge:create')"
|
||||
>创建充值订单</ElButton
|
||||
>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -80,21 +85,16 @@
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="目标店铺" prop="shop_id">
|
||||
<ElTreeSelect
|
||||
<ElFormItem label="店铺" prop="shop_id">
|
||||
<ElCascader
|
||||
v-model="createForm.shop_id"
|
||||
:data="shopTreeData"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择店铺"
|
||||
filterable
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
:props="{
|
||||
label: 'shop_name',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
}"
|
||||
style="width: 100%"
|
||||
@change="handleShopChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
@@ -154,7 +154,7 @@
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AgentRechargeService, ShopService } from '@/api/modules'
|
||||
import { ElMessage, ElTag, ElTreeSelect, ElButton } from 'element-plus'
|
||||
import { ElMessage, ElTag, ElTreeSelect, ElButton, ElCascader } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type {
|
||||
AgentRecharge,
|
||||
@@ -201,6 +201,37 @@
|
||||
const shopOptions = ref<any[]>([])
|
||||
const shopTreeData = ref<ShopResponse[]>([])
|
||||
|
||||
// 店铺级联选择相关
|
||||
const shopCascadeOptions = ref<any[]>([])
|
||||
const shopCascadeProps = {
|
||||
lazy: true,
|
||||
checkStrictly: true,
|
||||
lazyLoad: async (node: any, resolve: any) => {
|
||||
const { level, value } = node
|
||||
const parentId = level === 0 ? undefined : value
|
||||
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
||||
if (res.code === 0) {
|
||||
const nodes = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
resolve(nodes)
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺级联数据失败:', error)
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
@@ -427,26 +458,38 @@
|
||||
return tree
|
||||
}
|
||||
|
||||
// 加载店铺列表
|
||||
// 加载店铺列表 - 改用级联查询
|
||||
const loadShops = async () => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 9999 // 获取所有数据用于构建树形结构
|
||||
// 加载平铺列表用于搜索(保留原有逻辑)
|
||||
const res1 = await ShopService.getShops({ page: 1, page_size: 9999 })
|
||||
if (res1.code === 0) {
|
||||
shopOptions.value = res1.data.items || []
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
const items = res.data.items || []
|
||||
// 保留平铺列表用于搜索
|
||||
shopOptions.value = items
|
||||
// 构建树形数据用于创建对话框
|
||||
shopTreeData.value = buildTreeData(items)
|
||||
|
||||
// 加载顶级店铺用于级联选择
|
||||
const res2 = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||
if (res2.code === 0) {
|
||||
shopCascadeOptions.value = (res2.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load shops failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理店铺选择变化
|
||||
const handleShopChange = (value: any) => {
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
createForm.shop_id = value[value.length - 1]
|
||||
} else {
|
||||
createForm.shop_id = null
|
||||
}
|
||||
}
|
||||
|
||||
// 获取充值订单列表
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
@@ -590,12 +633,24 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (row: AgentRecharge) => {
|
||||
router.push({
|
||||
name: 'AgentRechargeDetailRoute',
|
||||
params: { id: row.id }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取操作按钮
|
||||
const getActions = (row: AgentRecharge) => {
|
||||
const actions: any[] = []
|
||||
|
||||
// 待支付且线下转账的订单可以确认支付
|
||||
if (row.status === 1 && row.payment_method === 'offline' && hasAuth('agent_recharge:confirm_payment')) {
|
||||
if (
|
||||
row.status === 1 &&
|
||||
row.payment_method === 'offline' &&
|
||||
hasAuth('agent_recharge:confirm_payment')
|
||||
) {
|
||||
actions.push({
|
||||
label: '确认支付',
|
||||
handler: () => handleShowConfirmPay(row),
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTable, ElTableColumn, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { OrderService } from '@/api/modules'
|
||||
@@ -60,7 +59,6 @@
|
||||
|
||||
defineOptions({ name: 'OrderDetail' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -74,24 +72,20 @@
|
||||
|
||||
// 获取订单类型文本
|
||||
const getOrderTypeText = (type: string): string => {
|
||||
return type === 'single_card'
|
||||
? t('orderManagement.orderType.singleCard')
|
||||
: t('orderManagement.orderType.device')
|
||||
return type === 'single_card' ? '单卡购买' : '设备购买'
|
||||
}
|
||||
|
||||
// 获取买家类型文本
|
||||
const getBuyerTypeText = (type: string): string => {
|
||||
return type === 'personal'
|
||||
? t('orderManagement.buyerType.personal')
|
||||
: t('orderManagement.buyerType.agent')
|
||||
return type === 'personal' ? '个人客户' : '代理商'
|
||||
}
|
||||
|
||||
// 获取支付方式文本
|
||||
const getPaymentMethodText = (method: string): string => {
|
||||
const methodMap: Record<string, string> = {
|
||||
wallet: t('orderManagement.paymentMethod.wallet'),
|
||||
wechat: t('orderManagement.paymentMethod.wechat'),
|
||||
alipay: t('orderManagement.paymentMethod.alipay')
|
||||
wallet: '钱包支付',
|
||||
wechat: '微信支付',
|
||||
alipay: '支付宝支付'
|
||||
}
|
||||
return methodMap[method] || method
|
||||
}
|
||||
@@ -99,9 +93,9 @@
|
||||
// 获取佣金状态文本
|
||||
const getCommissionStatusText = (status: number): string => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: t('orderManagement.commissionStatus.notApplicable'),
|
||||
1: t('orderManagement.commissionStatus.pending'),
|
||||
2: t('orderManagement.commissionStatus.settled')
|
||||
0: '不适用',
|
||||
1: '待结算',
|
||||
2: '已结算'
|
||||
}
|
||||
return statusMap[status] || '-'
|
||||
}
|
||||
@@ -122,17 +116,17 @@
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: t('orderManagement.table.orderNo'), prop: 'order_no' },
|
||||
{ label: '订单号', prop: 'order_no' },
|
||||
{
|
||||
label: t('orderManagement.table.orderType'),
|
||||
label: '订单类型',
|
||||
formatter: (_, data) => getOrderTypeText(data.order_type)
|
||||
},
|
||||
{
|
||||
label: t('orderManagement.table.paymentStatus'),
|
||||
label: '支付状态',
|
||||
formatter: (_, data) => data.payment_status_text || '-'
|
||||
},
|
||||
{
|
||||
label: t('orderManagement.table.totalAmount'),
|
||||
label: '订单金额',
|
||||
prop: 'total_amount',
|
||||
formatter: (value) => formatCurrency(value)
|
||||
},
|
||||
@@ -148,7 +142,7 @@
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{
|
||||
label: t('orderManagement.table.buyerType'),
|
||||
label: '买家类型',
|
||||
formatter: (_, data) => (data.buyer_type ? getBuyerTypeText(data.buyer_type) : '-')
|
||||
},
|
||||
{
|
||||
@@ -198,7 +192,7 @@
|
||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
||||
},
|
||||
{
|
||||
label: t('orderManagement.table.commissionStatus'),
|
||||
label: '佣金状态',
|
||||
formatter: (_, data) => getCommissionStatusText(data.commission_status)
|
||||
},
|
||||
{
|
||||
@@ -206,12 +200,12 @@
|
||||
prop: 'commission_config_version'
|
||||
},
|
||||
{
|
||||
label: t('orderManagement.table.createdAt'),
|
||||
label: '创建时间',
|
||||
prop: 'created_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
},
|
||||
{
|
||||
label: t('orderManagement.table.updatedAt'),
|
||||
label: '更新时间',
|
||||
prop: 'updated_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
}
|
||||
|
||||
@@ -176,6 +176,26 @@
|
||||
</template>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="createForm.payment_method === 'offline'"
|
||||
label="支付凭证"
|
||||
prop="payment_voucher_key"
|
||||
>
|
||||
<ElUpload
|
||||
ref="uploadRef"
|
||||
:auto-upload="false"
|
||||
:on-change="handleVoucherFileChange"
|
||||
:on-remove="handleRemoveVoucher"
|
||||
accept="image/*"
|
||||
list-type="picture-card"
|
||||
:limit="1"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 jpg、png 格式,文件大小不超过 5MB</div>
|
||||
</template>
|
||||
</ElUpload>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
@@ -265,9 +285,10 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { OrderService, CardService, DeviceService, PackageManageService } from '@/api/modules'
|
||||
import { OrderService, CardService, DeviceService, PackageManageService, StorageService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules, UploadInstance, UploadFile } from 'element-plus'
|
||||
import type {
|
||||
Order,
|
||||
OrderQueryParams,
|
||||
@@ -408,29 +429,104 @@
|
||||
]
|
||||
|
||||
const createFormRef = ref<FormInstance>()
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
|
||||
const createRules = reactive<FormRules>({
|
||||
order_type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择订单类型',
|
||||
trigger: 'change'
|
||||
// 处理支付凭证文件选择
|
||||
const handleVoucherFileChange = async (uploadFile: UploadFile) => {
|
||||
const file = uploadFile.raw
|
||||
if (!file) return
|
||||
|
||||
// 验证文件类型
|
||||
const isImage = file.type.startsWith('image/')
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件!')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isLt5M) {
|
||||
ElMessage.error('图片大小不能超过 5MB!')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
try {
|
||||
ElMessage.info('正在上传支付凭证...')
|
||||
|
||||
// 1. 获取上传 URL
|
||||
const uploadUrlRes = await StorageService.getUploadUrl({
|
||||
file_name: file.name,
|
||||
content_type: file.type,
|
||||
purpose: 'attachment'
|
||||
})
|
||||
|
||||
if (uploadUrlRes.code !== 0) {
|
||||
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
],
|
||||
package_ids: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择套餐',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
payment_method: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择支付方式',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
|
||||
const { upload_url, file_key } = uploadUrlRes.data
|
||||
|
||||
// 2. 上传文件到对象存储
|
||||
await StorageService.uploadFile(upload_url, file, file.type)
|
||||
|
||||
// 3. 保存 file_key
|
||||
createForm.payment_voucher_key = file_key
|
||||
ElMessage.success('上传成功')
|
||||
} catch (error: any) {
|
||||
console.error('上传失败:', error)
|
||||
ElMessage.error(error.message || '上传失败,请重试')
|
||||
createForm.payment_voucher_key = undefined
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除支付凭证
|
||||
const handleRemoveVoucher = () => {
|
||||
createForm.payment_voucher_key = undefined
|
||||
}
|
||||
|
||||
const createRules = computed<FormRules>(() => {
|
||||
const baseRules: FormRules = {
|
||||
order_type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择订单类型',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
package_ids: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择套餐',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
payment_method: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择支付方式',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 线下支付时,支付凭证为必填
|
||||
if (createForm.payment_method === 'offline') {
|
||||
baseRules.payment_voucher_key = [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传支付凭证',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return baseRules
|
||||
})
|
||||
|
||||
const createForm = reactive<CreateOrderRequest>({
|
||||
@@ -438,7 +534,8 @@
|
||||
package_ids: [],
|
||||
iot_card_id: null,
|
||||
device_id: null,
|
||||
payment_method: 'wallet' // 默认使用钱包支付
|
||||
payment_method: 'wallet', // 默认使用钱包支付
|
||||
payment_voucher_key: undefined // 线下支付凭证
|
||||
})
|
||||
|
||||
const orderList = ref<Order[]>([])
|
||||
@@ -884,6 +981,10 @@
|
||||
createForm.iot_card_id = null
|
||||
createForm.device_id = null
|
||||
createForm.payment_method = 'wallet'
|
||||
createForm.payment_voucher_key = undefined
|
||||
|
||||
// 清空上传文件列表
|
||||
uploadRef.value?.clearFiles()
|
||||
|
||||
// 清空套餐、IoT卡和设备搜索结果
|
||||
packageOptions.value = []
|
||||
@@ -964,6 +1065,11 @@
|
||||
payment_method: createForm.payment_method
|
||||
}
|
||||
|
||||
// 线下支付时,添加支付凭证
|
||||
if (createForm.payment_method === 'offline' && createForm.payment_voucher_key) {
|
||||
data.payment_voucher_key = createForm.payment_voucher_key
|
||||
}
|
||||
|
||||
await OrderService.createOrder(data)
|
||||
|
||||
// 根据支付方式显示不同的成功消息
|
||||
|
||||
@@ -690,15 +690,17 @@
|
||||
label: '虚流量比例',
|
||||
width: 120,
|
||||
formatter: (row: PackageResponse) => {
|
||||
// 如果启用虚流量且虚流量大于0,计算比例
|
||||
// 如果启用虚流量且真流量大于0,计算虚量百分比
|
||||
const virtualData = row.virtual_data_mb ?? 0
|
||||
const realData = row.real_data_mb ?? 0
|
||||
if (row.enable_virtual_data && virtualData > 0) {
|
||||
const ratio = realData / virtualData
|
||||
return ratio.toFixed(2)
|
||||
if (row.enable_virtual_data && realData > 0 && virtualData > 0) {
|
||||
// 虚量百分比 = (1 - 虚流量/真流量) * 100%
|
||||
// 例如:真流量100G,虚流量70G,则虚量百分比 = (1 - 70/100) * 100% = 30%
|
||||
const ratio = (1 - virtualData / realData) * 100
|
||||
return `${ratio.toFixed(2)}%`
|
||||
}
|
||||
// 否则返回 1.0
|
||||
return '1.00'
|
||||
// 否则返回 0%
|
||||
return '0%'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -174,13 +174,18 @@
|
||||
<ElFormItem label="成本价(元)" prop="cost_price_yuan">
|
||||
<ElInputNumber
|
||||
v-model="packageForm.cost_price_yuan"
|
||||
:min="0"
|
||||
:min="packageForm.original_cost_price || 0"
|
||||
:max="packageForm.original_cost_price ? packageForm.original_cost_price * 1.5 : undefined"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
style="width: 100%"
|
||||
placeholder="请输入成本价"
|
||||
/>
|
||||
<div v-if="packageForm.original_cost_price" class="form-tip">
|
||||
原价: ¥{{ packageForm.original_cost_price.toFixed(2) }},
|
||||
最高: ¥{{ (packageForm.original_cost_price * 1.5).toFixed(2) }} (50%溢价)
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
@@ -245,17 +250,16 @@
|
||||
<ElRow :gutter="20" v-if="form.series_id">
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="选择店铺" prop="shop_id">
|
||||
<ElTreeSelect
|
||||
<ElCascader
|
||||
v-model="form.shop_id"
|
||||
:data="shopTreeData"
|
||||
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择店铺"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择或搜索店铺"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
clearable
|
||||
:loading="shopLoading"
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
@change="handleShopChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -308,6 +312,7 @@
|
||||
<ElInputNumber
|
||||
v-model="pkg.cost_price"
|
||||
:min="pkg.original_cost_price || 0"
|
||||
:max="pkg.original_cost_price ? pkg.original_cost_price * 1.5 : undefined"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
@@ -315,7 +320,7 @@
|
||||
style="width: 150px"
|
||||
/>
|
||||
<span v-if="pkg.original_cost_price" class="min-cost-hint">
|
||||
(成本价: ¥{{ pkg.original_cost_price.toFixed(2) }})
|
||||
(原价: ¥{{ pkg.original_cost_price.toFixed(2) }}, 最高: ¥{{ (pkg.original_cost_price * 1.5).toFixed(2) }})
|
||||
</span>
|
||||
</div>
|
||||
<ElButton type="danger" size="small" @click="removePackage(index)"
|
||||
@@ -324,7 +329,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-tip"
|
||||
>设置每个套餐的成本价(单位:元),不能低于套餐原始成本价</div
|
||||
>设置每个套餐的成本价(单位:元),不能低于原价,不能高于原价的50%溢价</div
|
||||
>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
@@ -333,12 +338,13 @@
|
||||
<!-- 第二步:佣金和强制充值配置 -->
|
||||
<div v-show="currentStep === 1">
|
||||
<!-- 一次性佣金配置 -->
|
||||
<div class="form-section-title">
|
||||
<span class="title-text">一次性佣金配置</span>
|
||||
</div>
|
||||
<template v-if="form.enable_one_time_commission">
|
||||
<div class="form-section-title">
|
||||
<span class="title-text">一次性佣金配置</span>
|
||||
</div>
|
||||
|
||||
<!-- 佣金类型和金额 - 2列布局 -->
|
||||
<ElRow :gutter="20">
|
||||
<!-- 佣金类型和金额 - 2列布局 -->
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="佣金类型">
|
||||
<div class="commission-type-display">
|
||||
@@ -432,6 +438,7 @@
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 强制充值配置 -->
|
||||
<div class="form-section-title">
|
||||
@@ -497,12 +504,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 一次性佣金配置 -->
|
||||
<div class="form-section-title">
|
||||
<span class="title-text">一次性佣金配置</span>
|
||||
</div>
|
||||
<template v-if="form.enable_one_time_commission">
|
||||
<div class="form-section-title">
|
||||
<span class="title-text">一次性佣金配置</span>
|
||||
</div>
|
||||
|
||||
<!-- 佣金类型和金额 - 2列布局 -->
|
||||
<ElRow :gutter="20" v-if="form.series_id">
|
||||
<!-- 佣金类型和金额 - 2列布局 -->
|
||||
<ElRow :gutter="20" v-if="form.series_id">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="佣金类型">
|
||||
<div class="commission-type-display">
|
||||
@@ -596,6 +604,7 @@
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 强制充值配置 -->
|
||||
<div class="form-section-title">
|
||||
@@ -808,6 +817,7 @@
|
||||
package_name?: string
|
||||
package_code?: string
|
||||
cost_price_yuan: number
|
||||
original_cost_price?: number // 原始成本价(元)
|
||||
}>({
|
||||
cost_price_yuan: 0
|
||||
})
|
||||
@@ -819,7 +829,22 @@
|
||||
],
|
||||
cost_price_yuan: [
|
||||
{ required: true, message: '请输入成本价', trigger: 'blur' },
|
||||
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback(new Error('请输入成本价'))
|
||||
} else if (value < 0) {
|
||||
callback(new Error('成本价不能小于0'))
|
||||
} else if (packageForm.value.original_cost_price && value < packageForm.value.original_cost_price) {
|
||||
callback(new Error(`成本价不能低于原价 ¥${packageForm.value.original_cost_price.toFixed(2)}`))
|
||||
} else if (packageForm.value.original_cost_price && value > packageForm.value.original_cost_price * 1.5) {
|
||||
callback(new Error(`成本价不能高于原价的50%溢价(最高:¥${(packageForm.value.original_cost_price * 1.5).toFixed(2)})`))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
@@ -832,6 +857,37 @@
|
||||
const searchShopOptions = ref<ShopResponse[]>([])
|
||||
const searchAllocatorShopOptions = ref<ShopResponse[]>([])
|
||||
|
||||
// 店铺级联选择相关
|
||||
const shopCascadeOptions = ref<any[]>([])
|
||||
const shopCascadeProps = {
|
||||
lazy: true,
|
||||
checkStrictly: true, // 允许选择任意一级
|
||||
lazyLoad: async (node: any, resolve: any) => {
|
||||
const { level, value } = node
|
||||
const parentId = level === 0 ? undefined : value
|
||||
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
||||
if (res.code === 0) {
|
||||
const nodes = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
resolve(nodes)
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺级联数据失败:', error)
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
shop_id: undefined as number | undefined,
|
||||
@@ -944,8 +1000,9 @@
|
||||
series_name: '',
|
||||
shop_name: '',
|
||||
allocator_shop_name: '',
|
||||
enable_one_time_commission: false, // 是否启用一次性佣金(从系列继承)
|
||||
commission_type: 'fixed' as 'fixed' | 'tiered', // 佣金类型
|
||||
one_time_commission_amount: 0, // 固定佣金金额(元)
|
||||
one_time_commission_amount: undefined, // 固定佣金金额(元)
|
||||
series_max_commission_amount: 0, // 系列最大佣金金额(元)
|
||||
commission_tiers: [] as Array<{
|
||||
operator?: '>=' | '>' | '<=' | '<' // 比较运算符
|
||||
@@ -968,59 +1025,62 @@
|
||||
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 根据佣金类型添加验证规则
|
||||
if (form.commission_type === 'fixed') {
|
||||
baseRules.one_time_commission_amount = [
|
||||
{ required: true, message: '请输入固定佣金金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback(new Error('请输入固定佣金金额'))
|
||||
} else if (value < 0) {
|
||||
callback(new Error('佣金金额不能小于0'))
|
||||
} else if (
|
||||
form.series_max_commission_amount > 0 &&
|
||||
value > form.series_max_commission_amount
|
||||
) {
|
||||
callback(
|
||||
new Error(
|
||||
`佣金金额不能超过该系列最大值 ¥${form.series_max_commission_amount.toFixed(2)}`
|
||||
)
|
||||
)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
} else if (form.commission_type === 'tiered') {
|
||||
baseRules.commission_tiers = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请至少添加一个梯度配置'))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证每个梯度的佣金金额不超过最大值
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const tier = value[i]
|
||||
if (tier.max_amount && tier.amount > tier.max_amount) {
|
||||
// 只有启用一次性佣金时才添加佣金验证规则
|
||||
if (form.enable_one_time_commission) {
|
||||
// 根据佣金类型添加验证规则
|
||||
if (form.commission_type === 'fixed') {
|
||||
baseRules.one_time_commission_amount = [
|
||||
{ required: true, message: '请输入固定佣金金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback(new Error('请输入固定佣金金额'))
|
||||
} else if (value < 0) {
|
||||
callback(new Error('佣金金额不能小于0'))
|
||||
} else if (
|
||||
form.series_max_commission_amount > 0 &&
|
||||
value > form.series_max_commission_amount
|
||||
) {
|
||||
callback(
|
||||
new Error(
|
||||
`第${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}`
|
||||
`佣金金额不能超过该系列最大值 ¥${form.series_max_commission_amount.toFixed(2)}`
|
||||
)
|
||||
)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
} else if (form.commission_type === 'tiered') {
|
||||
baseRules.commission_tiers = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请至少添加一个梯度配置'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
// 验证每个梯度的佣金金额不超过最大值
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const tier = value[i]
|
||||
if (tier.max_amount && tier.amount > tier.max_amount) {
|
||||
callback(
|
||||
new Error(
|
||||
`第${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}`
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果启用了强制充值,添加验证规则
|
||||
@@ -1207,9 +1267,14 @@
|
||||
return tree
|
||||
}
|
||||
|
||||
// 店铺树节点过滤方法
|
||||
const filterShopNode = (value: string, data: any) => {
|
||||
if (!value) return true
|
||||
return data.shop_name.toLowerCase().includes(value.toLowerCase())
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSeriesOptions()
|
||||
loadShopOptions()
|
||||
loadSearchSeriesOptions()
|
||||
loadSearchShopOptions()
|
||||
loadSearchAllocatorShopOptions()
|
||||
@@ -1322,6 +1387,9 @@
|
||||
// 设置系列名称
|
||||
form.series_name = selectedSeries.series_name
|
||||
|
||||
// 获取系列是否启用一次性佣金
|
||||
form.enable_one_time_commission = selectedSeries.enable_one_time_commission || false
|
||||
|
||||
// 从系列配置中获取佣金类型
|
||||
const commissionConfig = selectedSeries.one_time_commission_config
|
||||
if (commissionConfig && commissionConfig.commission_type) {
|
||||
@@ -1329,14 +1397,14 @@
|
||||
|
||||
// 根据佣金类型初始化对应字段
|
||||
if (commissionConfig.commission_type === 'fixed') {
|
||||
form.one_time_commission_amount = 0
|
||||
form.one_time_commission_amount = undefined
|
||||
// 设置系列最大佣金金额(从分转换为元)
|
||||
form.series_max_commission_amount = commissionConfig.commission_amount
|
||||
? commissionConfig.commission_amount / 100
|
||||
: 0
|
||||
form.commission_tiers = [{ threshold: 0, amount: 0 }]
|
||||
} else if (commissionConfig.commission_type === 'tiered') {
|
||||
form.one_time_commission_amount = 0
|
||||
form.one_time_commission_amount = undefined
|
||||
form.series_max_commission_amount = 0
|
||||
// 梯度配置从系列继承,包含完整字段,佣金金额也默认继承
|
||||
if (commissionConfig.tiers && commissionConfig.tiers.length > 0) {
|
||||
@@ -1362,7 +1430,7 @@
|
||||
} else {
|
||||
// 默认固定佣金
|
||||
form.commission_type = 'fixed'
|
||||
form.one_time_commission_amount = 0
|
||||
form.one_time_commission_amount = undefined
|
||||
form.series_max_commission_amount = 0
|
||||
form.series_force_recharge_amount = 0
|
||||
}
|
||||
@@ -1374,7 +1442,7 @@
|
||||
// 未选择系列,重置佣金类型
|
||||
form.series_name = ''
|
||||
form.commission_type = 'fixed'
|
||||
form.one_time_commission_amount = 0
|
||||
form.one_time_commission_amount = undefined
|
||||
form.series_max_commission_amount = 0
|
||||
form.series_force_recharge_amount = 0
|
||||
form.commission_tiers = []
|
||||
@@ -1394,6 +1462,22 @@
|
||||
}
|
||||
)
|
||||
|
||||
// 监听套餐选择,自动设置原始成本价和成本价
|
||||
watch(
|
||||
() => packageForm.value.package_id,
|
||||
(packageId) => {
|
||||
if (packageDialogType.value === 'add' && packageId) {
|
||||
// 从 availablePackages 中查找选中的套餐
|
||||
const selectedPackage = availablePackages.value.find((p) => p.id === packageId)
|
||||
if (selectedPackage) {
|
||||
const originalCostPrice = selectedPackage.cost_price ? selectedPackage.cost_price / 100 : 0
|
||||
packageForm.value.original_cost_price = originalCostPrice
|
||||
packageForm.value.cost_price_yuan = originalCostPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 加载系列选项(用于新增对话框,默认加载10条)
|
||||
const loadSeriesOptions = async (seriesName?: string) => {
|
||||
seriesLoading.value = true
|
||||
@@ -1417,26 +1501,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载店铺选项(用于新增对话框,加载所有店铺并构建树形结构)
|
||||
const loadShopOptions = async () => {
|
||||
shopLoading.value = true
|
||||
try {
|
||||
// 加载所有店铺,不分页
|
||||
const res = await ShopService.getShops({
|
||||
page: 1,
|
||||
page_size: 10000 // 使用较大的值获取所有店铺
|
||||
})
|
||||
if (res.code === 0) {
|
||||
shopOptions.value = res.data.items
|
||||
// 构建树形结构数据
|
||||
shopTreeData.value = buildTreeData(shopOptions.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺选项失败:', error)
|
||||
} finally {
|
||||
shopLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载搜索栏系列选项(默认加载10条)
|
||||
const loadSearchSeriesOptions = async () => {
|
||||
@@ -1487,6 +1551,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载店铺列表(用于新增对话框) - 改用级联查询
|
||||
const loadShopListForDialog = async () => {
|
||||
shopLoading.value = true
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||
if (res.code === 0) {
|
||||
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺列表失败:', error)
|
||||
} finally {
|
||||
shopLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理店铺选择变化
|
||||
const handleShopChange = (value: any) => {
|
||||
// 级联选择器返回的是数组,取最后一个值作为 shop_id
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
form.shop_id = value[value.length - 1]
|
||||
} else {
|
||||
form.shop_id = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索系列(用于搜索栏)
|
||||
const handleSearchSeries = async (query: string) => {
|
||||
if (!query) {
|
||||
@@ -1608,6 +1701,9 @@
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
// 每次打开对话框时重新加载店铺列表,确保获取最新的店铺数据
|
||||
await loadShopListForDialog()
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
// 编辑模式:先调用详情接口获取完整数据
|
||||
loading.value = true
|
||||
@@ -1628,9 +1724,13 @@
|
||||
// 获取系列配置信息
|
||||
const selectedSeries = seriesOptions.value.find((s) => s.id === detail.series_id)
|
||||
|
||||
// 设置是否启用一次性佣金
|
||||
form.enable_one_time_commission = selectedSeries?.enable_one_time_commission || false
|
||||
|
||||
// 设置佣金配置
|
||||
if (detail.commission_type === 'fixed') {
|
||||
form.one_time_commission_amount = detail.one_time_commission_amount / 100
|
||||
const amount = detail.one_time_commission_amount / 100
|
||||
form.one_time_commission_amount = amount > 0 ? amount : undefined
|
||||
// 设置系列最大佣金金额
|
||||
if (selectedSeries?.one_time_commission_config?.commission_amount) {
|
||||
form.series_max_commission_amount =
|
||||
@@ -1711,8 +1811,9 @@
|
||||
form.series_name = ''
|
||||
form.shop_name = ''
|
||||
form.allocator_shop_name = ''
|
||||
form.enable_one_time_commission = false
|
||||
form.commission_type = 'fixed'
|
||||
form.one_time_commission_amount = 0
|
||||
form.one_time_commission_amount = undefined
|
||||
form.series_max_commission_amount = 0
|
||||
form.series_force_recharge_amount = 0
|
||||
form.commission_tiers = []
|
||||
@@ -1742,8 +1843,9 @@
|
||||
form.series_name = ''
|
||||
form.shop_name = ''
|
||||
form.allocator_shop_name = ''
|
||||
form.enable_one_time_commission = false
|
||||
form.commission_type = 'fixed'
|
||||
form.one_time_commission_amount = 0
|
||||
form.one_time_commission_amount = undefined
|
||||
form.series_max_commission_amount = 0
|
||||
form.series_force_recharge_amount = 0
|
||||
form.commission_tiers = []
|
||||
@@ -1803,17 +1905,20 @@
|
||||
// 构建请求数据
|
||||
const data: any = {}
|
||||
|
||||
// 根据佣金类型携带不同参数
|
||||
if (form.commission_type === 'fixed') {
|
||||
// 固定模式:携带 one_time_commission_amount(将元转换为分)
|
||||
data.one_time_commission_amount = Math.round(form.one_time_commission_amount * 100)
|
||||
} else if (form.commission_type === 'tiered') {
|
||||
// 梯度模式:携带 commission_tiers(将元转换为分)
|
||||
// 注意:请求时不传 operator,operator 是响应中从 PackageSeries 合并过来的
|
||||
data.commission_tiers = form.commission_tiers.map((tier: CommissionTier) => ({
|
||||
threshold: tier.threshold,
|
||||
amount: Math.round(tier.amount * 100) // 将元转换为分
|
||||
}))
|
||||
// 只有启用一次性佣金时才传递佣金相关参数
|
||||
if (form.enable_one_time_commission) {
|
||||
// 根据佣金类型携带不同参数
|
||||
if (form.commission_type === 'fixed') {
|
||||
// 固定模式:携带 one_time_commission_amount(将元转换为分)
|
||||
data.one_time_commission_amount = Math.round((form.one_time_commission_amount || 0) * 100)
|
||||
} else if (form.commission_type === 'tiered') {
|
||||
// 梯度模式:携带 commission_tiers(将元转换为分)
|
||||
// 注意:请求时不传 operator,operator 是响应中从 PackageSeries 合并过来的
|
||||
data.commission_tiers = form.commission_tiers.map((tier: CommissionTier) => ({
|
||||
threshold: tier.threshold,
|
||||
amount: Math.round(tier.amount * 100) // 将元转换为分
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// 强制充值配置(无论开关状态都要传递)
|
||||
@@ -1828,9 +1933,30 @@
|
||||
|
||||
// 可选:套餐配置(将元转换为分)
|
||||
if (form.packages.length > 0) {
|
||||
// 验证成本价是否在允许范围内
|
||||
for (const pkg of form.packages) {
|
||||
const costPrice = pkg.cost_price ?? 0
|
||||
const originalCostPrice = pkg.original_cost_price ?? 0
|
||||
|
||||
if (originalCostPrice > 0 && costPrice > originalCostPrice * 1.5) {
|
||||
ElMessage.error(
|
||||
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能高于原价的50%溢价(最高:¥${(originalCostPrice * 1.5).toFixed(2)})`
|
||||
)
|
||||
submitLoading.value = false
|
||||
return
|
||||
}
|
||||
if (originalCostPrice > 0 && costPrice < originalCostPrice) {
|
||||
ElMessage.error(
|
||||
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能低于原价(¥${originalCostPrice.toFixed(2)})`
|
||||
)
|
||||
submitLoading.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data.packages = form.packages.map((pkg: any) => ({
|
||||
package_id: pkg.package_id,
|
||||
cost_price: Math.round(pkg.cost_price * 100) // 将元转换为分
|
||||
cost_price: Math.round((pkg.cost_price ?? 0) * 100) // 将元转换为分
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1912,11 +2038,17 @@
|
||||
// 显示编辑套餐对话框
|
||||
const showEditPackageDialog = (row: GrantPackageInfo) => {
|
||||
packageDialogType.value = 'edit'
|
||||
|
||||
// 从 packageOptions 中查找原始成本价
|
||||
const pkgOption = packageOptions.value.find((p) => p.id === row.package_id)
|
||||
const originalCostPrice = pkgOption?.cost_price ? pkgOption.cost_price / 100 : 0
|
||||
|
||||
packageForm.value = {
|
||||
package_id: row.package_id,
|
||||
package_name: row.package_name,
|
||||
package_code: row.package_code,
|
||||
cost_price_yuan: row.cost_price / 100
|
||||
cost_price_yuan: row.cost_price / 100,
|
||||
original_cost_price: originalCostPrice
|
||||
}
|
||||
|
||||
packageDialogVisible.value = true
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -31,9 +32,6 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="false"
|
||||
:pagination="false"
|
||||
:actions="getActions"
|
||||
:actionsWidth="160"
|
||||
@selection-change="handleSelectionChange"
|
||||
@@ -76,20 +74,15 @@
|
||||
<ElRow :gutter="20" v-if="dialogType === 'add'">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="上级店铺" prop="parent_id">
|
||||
<ElTreeSelect
|
||||
<ElCascader
|
||||
v-model="formData.parent_id"
|
||||
:data="parentShopTreeData"
|
||||
:options="parentShopCascadeOptions"
|
||||
:props="cascadeProps"
|
||||
placeholder="一级店铺可不选"
|
||||
filterable
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
:props="{
|
||||
label: 'shop_name',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
}"
|
||||
style="width: 100%"
|
||||
@change="handleParentShopChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -287,7 +280,6 @@
|
||||
ElSwitch,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElTreeSelect,
|
||||
ElCascader
|
||||
} from 'element-plus'
|
||||
import { ref, reactive, computed, nextTick, onMounted } from 'vue'
|
||||
@@ -312,12 +304,41 @@
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const parentShopLoading = ref(false)
|
||||
const parentShopList = ref<ShopResponse[]>([])
|
||||
const parentShopTreeData = ref<ShopResponse[]>([])
|
||||
const defaultRoleLoading = ref(false)
|
||||
const defaultRoleList = ref<any[]>([])
|
||||
|
||||
// 级联选择相关
|
||||
const parentShopCascadeOptions = ref<any[]>([])
|
||||
const cascadeProps = {
|
||||
lazy: true,
|
||||
checkStrictly: true, // 允许选择任意一级
|
||||
lazyLoad: async (node: any, resolve: any) => {
|
||||
const { level, value } = node
|
||||
// level 0 表示根节点,不传 parent_id;level > 0 传 parent_id
|
||||
const parentId = level === 0 ? undefined : value
|
||||
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
||||
if (res.code === 0) {
|
||||
const nodes = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
resolve(nodes)
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺级联数据失败:', error)
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
// 默认角色管理相关状态
|
||||
const defaultRolesDialogVisible = ref(false)
|
||||
const defaultRolesLoading = ref(false)
|
||||
@@ -330,6 +351,7 @@
|
||||
const initialSearchState = {
|
||||
shop_name: '',
|
||||
shop_code: '',
|
||||
parent_shop_name: '',
|
||||
parent_id: undefined as number | undefined,
|
||||
level: undefined as number | undefined,
|
||||
status: undefined as number | undefined
|
||||
@@ -386,24 +408,6 @@
|
||||
placeholder: '请输入店铺编号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '上级店铺',
|
||||
prop: 'parent_id',
|
||||
type: 'tree-select',
|
||||
config: {
|
||||
data: parentShopTreeData.value,
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
checkStrictly: true,
|
||||
renderAfterExpand: false,
|
||||
props: {
|
||||
label: 'shop_name',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
},
|
||||
placeholder: '请选择上级店铺'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
@@ -420,6 +424,7 @@
|
||||
const columnOptions = [
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '店铺编号', prop: 'shop_code' },
|
||||
{ label: '上级店铺', prop: 'parent_shop_name' },
|
||||
{ label: '所在地区', prop: 'region' },
|
||||
{ label: '联系人', prop: 'contact_name' },
|
||||
{ label: '联系电话', prop: 'contact_phone' },
|
||||
@@ -458,9 +463,6 @@
|
||||
formData.init_phone = ''
|
||||
formData.default_role_id = undefined
|
||||
} else {
|
||||
// 新增模式下重新获取上级店铺列表
|
||||
await loadParentShopList()
|
||||
|
||||
formData.id = 0
|
||||
formData.shop_name = ''
|
||||
formData.shop_code = ''
|
||||
@@ -499,8 +501,8 @@
|
||||
await ShopService.deleteShop(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await getShopList()
|
||||
// 删除成功后也更新上级店铺列表
|
||||
await loadParentShopList()
|
||||
// 删除成功后也重新加载顶级店铺
|
||||
await loadTopLevelShops()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
@@ -524,6 +526,13 @@
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'parent_shop_name',
|
||||
label: '上级店铺',
|
||||
width: 150,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: ShopResponse) => row.parent_shop_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'region',
|
||||
label: '所在地区',
|
||||
@@ -621,7 +630,7 @@
|
||||
id: 0,
|
||||
shop_name: '',
|
||||
shop_code: '',
|
||||
parent_id: undefined as number | undefined,
|
||||
parent_id: undefined as number | number[] | undefined, // 支持级联选择器的数组格式
|
||||
region: [] as string[], // 省市区级联数据
|
||||
province: '',
|
||||
city: '',
|
||||
@@ -682,54 +691,55 @@
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getShopList()
|
||||
loadParentShopList()
|
||||
searchDefaultRoles('') // 加载初始默认角色列表
|
||||
})
|
||||
|
||||
// 加载上级店铺列表(用于新增对话框和搜索栏,获取所有店铺构建树形结构)
|
||||
const loadParentShopList = async (shopName?: string) => {
|
||||
parentShopLoading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 9999 // 获取所有数据用于构建树形结构
|
||||
}
|
||||
if (shopName) {
|
||||
params.shop_name = shopName
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
const items = res.data.items || []
|
||||
parentShopList.value = items
|
||||
// 构建树形数据
|
||||
parentShopTreeData.value = buildTreeData(items)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取上级店铺列表失败:', error)
|
||||
} finally {
|
||||
parentShopLoading.value = false
|
||||
// 处理级联选择变化
|
||||
const handleParentShopChange = (value: any) => {
|
||||
// 级联选择器返回的是数组,取最后一个值作为 parent_id
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
formData.parent_id = value[value.length - 1]
|
||||
} else {
|
||||
formData.parent_id = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 加载顶级店铺数据(用于级联选择器初始化)
|
||||
const loadTopLevelShops = async () => {
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||
if (res.code === 0) {
|
||||
parentShopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载顶级店铺失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getShopList()
|
||||
loadTopLevelShops() // 加载顶级店铺用于级联选择
|
||||
searchDefaultRoles('') // 加载初始默认角色列表
|
||||
})
|
||||
|
||||
// 获取店铺列表
|
||||
const getShopList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
page_size: 9999, // 获取所有数据用于构建树形结构
|
||||
page: pagination.currentPage,
|
||||
page_size: pagination.pageSize,
|
||||
shop_name: searchForm.shop_name || undefined,
|
||||
shop_code: searchForm.shop_code || undefined,
|
||||
parent_shop_name: searchForm.parent_shop_name || undefined,
|
||||
parent_id: searchForm.parent_id,
|
||||
level: searchForm.level,
|
||||
status: searchForm.status
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
const items = res.data.items || []
|
||||
tableData.value = buildTreeData(items)
|
||||
tableData.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -739,33 +749,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 构建树形数据
|
||||
const buildTreeData = (items: ShopResponse[]) => {
|
||||
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
|
||||
const tree: ShopResponse[] = []
|
||||
|
||||
// 先将所有项放入 map
|
||||
items.forEach((item) => {
|
||||
map.set(item.id, { ...item, children: [] })
|
||||
})
|
||||
|
||||
// 构建树形结构
|
||||
items.forEach((item) => {
|
||||
const node = map.get(item.id)!
|
||||
if (item.parent_id && map.has(item.parent_id)) {
|
||||
// 有父节点,添加到父节点的 children 中
|
||||
const parent = map.get(item.parent_id)!
|
||||
if (!parent.children) parent.children = []
|
||||
parent.children.push(node)
|
||||
} else {
|
||||
// 没有父节点或父节点不存在,作为根节点
|
||||
tree.push(node)
|
||||
}
|
||||
})
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
getShopList()
|
||||
}
|
||||
@@ -825,8 +808,12 @@
|
||||
default_role_id: formData.default_role_id
|
||||
}
|
||||
|
||||
// 可选字段
|
||||
if (formData.parent_id) data.parent_id = formData.parent_id
|
||||
// 可选字段 - parent_id 可能是数组(级联选择器)或数字
|
||||
if (formData.parent_id) {
|
||||
data.parent_id = Array.isArray(formData.parent_id)
|
||||
? formData.parent_id[formData.parent_id.length - 1]
|
||||
: formData.parent_id
|
||||
}
|
||||
if (formData.province) data.province = formData.province
|
||||
if (formData.city) data.city = formData.city
|
||||
if (formData.district) data.district = formData.district
|
||||
@@ -856,8 +843,8 @@
|
||||
|
||||
dialogVisible.value = false
|
||||
await getShopList()
|
||||
// 提交成功后也更新上级店铺列表
|
||||
await loadParentShopList()
|
||||
// 提交成功后也重新加载顶级店铺
|
||||
await loadTopLevelShops()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
|
||||
@@ -830,19 +830,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 检查节点是否有子节点(子菜单或按钮)
|
||||
const hasChildren = (data: any): boolean => {
|
||||
return data.children && data.children.length > 0
|
||||
}
|
||||
|
||||
// 移除单个权限
|
||||
const removeSinglePermission = async (data: any) => {
|
||||
// 检查是否有子菜单或按钮
|
||||
if (hasChildren(data)) {
|
||||
ElMessage.warning('该权限下还有子菜单或按钮,请先移除子项后再移除此权限')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存右侧树的展开节点
|
||||
const expandedKeys = rightTreeRef.value?.store?.nodesMap
|
||||
@@ -851,10 +840,16 @@
|
||||
.map((key) => Number(key))
|
||||
: []
|
||||
|
||||
await RoleService.removePermission(currentRoleId.value, data.id)
|
||||
// 获取要移除的所有权限ID(包括当前节点和所有子节点)
|
||||
const idsToRemove = [data.id, ...getAllChildrenIds(data.id)]
|
||||
|
||||
// 更新已选权限列表
|
||||
selectedPermissions.value = selectedPermissions.value.filter((id) => id !== data.id)
|
||||
// 调用API批量移除权限(传递所有需要移除的权限ID)
|
||||
await RoleService.removePermissions(currentRoleId.value, idsToRemove)
|
||||
|
||||
// 更新已选权限列表(移除当前节点及其所有子节点)
|
||||
selectedPermissions.value = selectedPermissions.value.filter(
|
||||
(id) => !idsToRemove.includes(id)
|
||||
)
|
||||
|
||||
// 重新构建左右两侧树
|
||||
const fullTreeData = buildTreeData(originalPermissionTree.value)
|
||||
|
||||
Reference in New Issue
Block a user