2008 lines
68 KiB
Vue
2008 lines
68 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="series-grants-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="searchForm"
|
||
:items="searchFormItems"
|
||
:show-expand="true"
|
||
label-width="85"
|
||
@reset="handleReset"
|
||
@search="handleSearch"
|
||
></ArtSearchBar>
|
||
|
||
<ElCard shadow="never" class="art-table-card">
|
||
<!-- 表格头部 -->
|
||
<ArtTableHeader
|
||
:columnList="columnOptions"
|
||
v-model:columns="columnChecks"
|
||
@refresh="handleRefresh"
|
||
>
|
||
<template #left>
|
||
<ElButton type="primary" @click="showDialog('add')" v-permission="'series_grants:add'"
|
||
>新增代理系列授权</ElButton
|
||
>
|
||
</template>
|
||
</ArtTableHeader>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="loading"
|
||
:data="allocationList"
|
||
:currentPage="pagination.page"
|
||
:pageSize="pagination.page_size"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
:actions="getActions"
|
||
:actionsWidth="160"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||
</template>
|
||
</ArtTable>
|
||
|
||
<!-- 新增/编辑对话框 -->
|
||
<ElDialog
|
||
v-model="dialogVisible"
|
||
:title="dialogType === 'add' ? '新增代理系列授权' : '编辑代理系列授权'"
|
||
width="50%"
|
||
:close-on-click-modal="false"
|
||
@closed="handleDialogClosed"
|
||
>
|
||
<!-- 新增模式:分步骤表单 -->
|
||
<template v-if="dialogType === 'add'">
|
||
<!-- 步骤条 -->
|
||
<ElSteps
|
||
:active="currentStep"
|
||
finish-status="success"
|
||
align-center
|
||
style="margin-bottom: 30px"
|
||
>
|
||
<ElStep title="基本信息" description="选择系列和店铺" />
|
||
<ElStep title="佣金和充值配置" description="设置佣金和强制充值" />
|
||
</ElSteps>
|
||
|
||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="130px">
|
||
<!-- 第一步:基本信息 -->
|
||
<div v-show="currentStep === 0">
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="24">
|
||
<ElFormItem label="选择套餐系列" prop="series_id">
|
||
<ElSelect
|
||
v-model="form.series_id"
|
||
placeholder="请选择套餐系列"
|
||
style="width: 100%"
|
||
filterable
|
||
remote
|
||
:remote-method="searchSeries"
|
||
:loading="seriesLoading"
|
||
clearable
|
||
>
|
||
<ElOption
|
||
v-for="series in seriesOptions"
|
||
:key="series.id"
|
||
:label="`${series.series_name} (${series.series_code})`"
|
||
:value="series.id"
|
||
/>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElRow :gutter="20" v-if="form.series_id">
|
||
<ElCol :span="24">
|
||
<ElFormItem label="选择店铺" prop="shop_id">
|
||
<ElCascader
|
||
v-model="form.shop_id"
|
||
:options="shopCascadeOptions"
|
||
:props="shopCascadeProps"
|
||
placeholder="请选择或搜索店铺"
|
||
style="width: 100%"
|
||
filterable
|
||
clearable
|
||
:loading="shopLoading"
|
||
@change="handleShopChange"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<!-- 套餐配置 -->
|
||
<div v-if="form.shop_id" class="form-section-title">
|
||
<span class="title-text">套餐配置(可选)</span>
|
||
</div>
|
||
|
||
<template v-if="form.shop_id">
|
||
<!-- 选择套餐 -->
|
||
<ElFormItem label="选择套餐">
|
||
<ElSelect
|
||
v-model="selectedPackageIds"
|
||
placeholder="请选择套餐"
|
||
style="width: 100%"
|
||
multiple
|
||
filterable
|
||
remote
|
||
:remote-method="searchPackages"
|
||
:loading="packageLoading"
|
||
clearable
|
||
>
|
||
<template
|
||
v-if="packageOptions.length === 0 && !packageLoading && form.series_id"
|
||
>
|
||
<ElOption disabled value="" label="该系列没有可选套餐" />
|
||
</template>
|
||
<ElOption
|
||
v-for="pkg in packageOptions"
|
||
:key="pkg.id"
|
||
:label="pkg.package_name"
|
||
:value="pkg.id"
|
||
/>
|
||
</ElSelect>
|
||
<div class="form-tip">选择该授权下包含的套餐</div>
|
||
</ElFormItem>
|
||
|
||
<!-- 套餐成本价 -->
|
||
<ElFormItem label="套餐成本价" v-if="form.packages.length > 0">
|
||
<div class="package-list">
|
||
<div
|
||
v-for="(pkg, index) in form.packages"
|
||
:key="pkg.package_id"
|
||
class="package-item"
|
||
>
|
||
<span class="package-name">{{ getPackageName(pkg.package_id) }}</span>
|
||
<div class="cost-price-input-wrapper">
|
||
<ElInputNumber
|
||
v-model="pkg.cost_price"
|
||
:min="pkg.original_cost_price || 0"
|
||
:max="getPackageCostPriceMax(pkg)"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
placeholder="成本价(元)"
|
||
style="width: 150px"
|
||
/>
|
||
<span v-if="pkg.original_cost_price" class="min-cost-hint">
|
||
(成本价: ¥{{ pkg.original_cost_price.toFixed(2) }}, 建议售价:
|
||
{{ formatSuggestedRetailPrice(pkg.suggested_retail_price) }})
|
||
</span>
|
||
</div>
|
||
<ElButton type="danger" size="small" @click="removePackage(index)"
|
||
>删除</ElButton
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="form-tip"
|
||
>设置每个套餐的成本价(单位:元),不能低于成本价,不能高于建议售价的50%溢价</div
|
||
>
|
||
</ElFormItem>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 第二步:佣金和强制充值配置 -->
|
||
<div v-show="currentStep === 1">
|
||
<!-- 一次性佣金配置 -->
|
||
<template v-if="form.enable_one_time_commission">
|
||
<div class="form-section-title">
|
||
<span class="title-text">一次性佣金配置</span>
|
||
</div>
|
||
|
||
<!-- 佣金类型和金额 - 2列布局 -->
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="佣金类型">
|
||
<div class="commission-type-display">
|
||
<ElTag
|
||
:type="form.commission_type === 'fixed' ? 'success' : 'warning'"
|
||
size="large"
|
||
>
|
||
{{ form.commission_type === 'fixed' ? '固定佣金' : '梯度佣金' }}
|
||
</ElTag>
|
||
<span class="type-hint">(从套餐系列配置继承)</span>
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12" v-if="form.commission_type === 'fixed'">
|
||
<ElFormItem label="佣金金额(元)" prop="one_time_commission_amount">
|
||
<ElInputNumber
|
||
v-model="form.one_time_commission_amount"
|
||
:min="0"
|
||
:max="form.series_max_commission_amount"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
style="width: 100%"
|
||
placeholder="请输入固定佣金金额(元)"
|
||
/>
|
||
<div class="form-tip">
|
||
该代理能获得的固定佣金金额(单位:元)
|
||
<span class="max-amount-hint">
|
||
<br />
|
||
该系列最大佣金金额:
|
||
<span class="amount-value"
|
||
>¥{{ form.series_max_commission_amount.toFixed(2) }}</span
|
||
>
|
||
</span>
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<!-- 梯度佣金配置 -->
|
||
<template v-if="form.commission_type === 'tiered'">
|
||
<ElFormItem label="梯度配置" prop="commission_tiers">
|
||
<ElTable :data="form.commission_tiers" border style="width: 100%">
|
||
<ElTableColumn label="比较运算符" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="success">{{ row.operator || '>=' }}</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="达标阈值" width="120">
|
||
<template #default="{ row }">
|
||
<span class="readonly-value">{{ row.threshold }}</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="统计维度" width="120">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="info">
|
||
{{ row.dimension === 'sales_count' ? '销量' : '销售额' }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="统计范围" width="140">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="warning"> 仅自己 </ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="佣金金额(元)" min-width="180">
|
||
<template #default="{ row }">
|
||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||
<ElInputNumber
|
||
v-model="row.amount"
|
||
:min="0"
|
||
:max="row.max_amount"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
placeholder="请输入佣金金额"
|
||
style="width: 100%"
|
||
/>
|
||
<span
|
||
v-if="row.max_amount"
|
||
style="font-size: 12px; color: var(--el-text-color-secondary)"
|
||
>
|
||
最大: ¥{{ row.max_amount.toFixed(2) }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
<div class="form-tip" style="margin-top: 8px">
|
||
梯度配置从套餐系列继承,达标阈值、统计维度、统计范围为只读,只能修改佣金金额
|
||
</div>
|
||
</ElFormItem>
|
||
</template>
|
||
</template>
|
||
|
||
<!-- 强制充值配置 -->
|
||
<div class="form-section-title">
|
||
<span class="title-text">强制充值配置(可选)</span>
|
||
</div>
|
||
|
||
<!-- 启用强制充值和强充金额 - 2列布局 -->
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="启用强制充值">
|
||
<ElSwitch
|
||
v-model="form.enable_force_recharge"
|
||
:disabled="form.force_recharge_locked && isAgentAccount"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol
|
||
:span="12"
|
||
v-if="
|
||
form.enable_force_recharge && !(form.force_recharge_locked && isAgentAccount)
|
||
"
|
||
>
|
||
<ElFormItem label="强充金额(元)" prop="force_recharge_amount">
|
||
<ElInputNumber
|
||
v-model="form.force_recharge_amount"
|
||
:min="0"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
style="width: 100%"
|
||
placeholder="请输入强制充值金额(元)"
|
||
/>
|
||
<div class="form-tip">
|
||
用户需要达到的强制充值金额
|
||
<span
|
||
v-if="form.series_name && form.series_force_recharge_amount > 0"
|
||
class="series-force-hint"
|
||
>
|
||
<br />
|
||
可参考
|
||
<span class="amount-value">{{ form.series_name }}</span
|
||
>系列强充金额:
|
||
<span class="amount-value"
|
||
>¥{{ form.series_force_recharge_amount.toFixed(2) }}</span
|
||
>
|
||
</span>
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</div>
|
||
</ElForm>
|
||
</template>
|
||
|
||
<!-- 编辑模式:原有的表单布局 -->
|
||
<ElForm v-else ref="formRef" :model="form" :rules="rules" label-width="130px">
|
||
<!-- 编辑模式:显示只读信息 -->
|
||
<div class="info-row">
|
||
<div class="info-item">
|
||
<span class="info-label">系列名称:</span>
|
||
<span class="info-value">{{ form.series_name || '-' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">店铺名称:</span>
|
||
<span class="info-value">{{ form.shop_name || '-' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">分配者店铺:</span>
|
||
<span class="info-value">{{ form.allocator_shop_name || '-' }}</span>
|
||
</div>
|
||
</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">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="佣金类型">
|
||
<div class="commission-type-display">
|
||
<ElTag
|
||
:type="form.commission_type === 'fixed' ? 'success' : 'warning'"
|
||
size="large"
|
||
>
|
||
{{ form.commission_type === 'fixed' ? '固定佣金' : '梯度佣金' }}
|
||
</ElTag>
|
||
<span class="type-hint">(从套餐系列配置继承)</span>
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12" v-if="form.commission_type === 'fixed'">
|
||
<ElFormItem label="佣金金额(元)" prop="one_time_commission_amount">
|
||
<ElInputNumber
|
||
v-model="form.one_time_commission_amount"
|
||
:min="0"
|
||
:max="form.series_max_commission_amount"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
style="width: 100%"
|
||
placeholder="请输入固定佣金金额(元)"
|
||
/>
|
||
<div class="form-tip">
|
||
该代理能获得的固定佣金金额(单位:元)
|
||
<span class="max-amount-hint">
|
||
<br />
|
||
该系列最大佣金金额:
|
||
<span class="amount-value"
|
||
>¥{{ form.series_max_commission_amount.toFixed(2) }}</span
|
||
>
|
||
</span>
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<!-- 梯度佣金配置 -->
|
||
<template v-if="form.commission_type === 'tiered'">
|
||
<ElFormItem label="梯度配置" prop="commission_tiers">
|
||
<ElTable :data="form.commission_tiers" border style="width: 100%">
|
||
<ElTableColumn label="比较运算符" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="success">{{ row.operator || '>=' }}</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="达标阈值" width="120">
|
||
<template #default="{ row }">
|
||
<span class="readonly-value">{{ row.threshold }}</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="统计维度" width="120">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="info">
|
||
{{ row.dimension === 'sales_count' ? '销量' : '销售额' }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="统计范围" width="140">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="warning"> 仅自己 </ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="佣金金额(元)" min-width="180">
|
||
<template #default="{ row }">
|
||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||
<ElInputNumber
|
||
v-model="row.amount"
|
||
:min="0"
|
||
:max="row.max_amount"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
placeholder="请输入佣金金额"
|
||
style="width: 100%"
|
||
/>
|
||
<span
|
||
v-if="row.max_amount"
|
||
style="font-size: 12px; color: var(--el-text-color-secondary)"
|
||
>
|
||
最大: ¥{{ row.max_amount.toFixed(2) }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
<div class="form-tip" style="margin-top: 8px">
|
||
梯度配置从套餐系列继承,达标阈值、统计维度、统计范围为只读,只能修改佣金金额
|
||
</div>
|
||
</ElFormItem>
|
||
</template>
|
||
</template>
|
||
|
||
<!-- 强制充值配置 -->
|
||
<div class="form-section-title">
|
||
<span class="title-text">强制充值配置(可选)</span>
|
||
</div>
|
||
|
||
<!-- 启用强制充值和强充金额 - 2列布局 -->
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="启用强制充值">
|
||
<ElSwitch
|
||
v-model="form.enable_force_recharge"
|
||
:disabled="form.force_recharge_locked && isAgentAccount"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol
|
||
:span="12"
|
||
v-if="form.enable_force_recharge && !(form.force_recharge_locked && isAgentAccount)"
|
||
>
|
||
<ElFormItem label="强充金额(元)" prop="force_recharge_amount">
|
||
<ElInputNumber
|
||
v-model="form.force_recharge_amount"
|
||
:min="0"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
style="width: 100%"
|
||
placeholder="请输入强制充值金额(元)"
|
||
/>
|
||
<div class="form-tip">
|
||
用户需要达到的强制充值金额
|
||
<span
|
||
v-if="form.series_name && form.series_force_recharge_amount > 0"
|
||
class="series-force-hint"
|
||
>
|
||
<br />
|
||
可参考
|
||
<span class="amount-value">{{ form.series_name }}</span
|
||
>系列强充金额:
|
||
<span class="amount-value"
|
||
>¥{{ form.series_force_recharge_amount.toFixed(2) }}</span
|
||
>
|
||
</span>
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</ElForm>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<template v-if="dialogType === 'add'">
|
||
<ElButton v-if="currentStep > 0" @click="handlePrevStep">上一步</ElButton>
|
||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||
<ElButton v-if="currentStep < 1" type="primary" @click="handleNextStep"
|
||
>下一步</ElButton
|
||
>
|
||
<ElButton
|
||
v-else
|
||
type="primary"
|
||
@click="handleSubmit(formRef)"
|
||
:loading="submitLoading"
|
||
>
|
||
提交
|
||
</ElButton>
|
||
</template>
|
||
<template v-else>
|
||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
||
提交
|
||
</ElButton>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h, ref, reactive, computed, watch, onMounted, nextTick } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import {
|
||
ShopSeriesGrantService,
|
||
PackageSeriesService,
|
||
ShopService,
|
||
PackageManageService
|
||
} from '@/api/modules'
|
||
import {
|
||
ElMessage,
|
||
ElMessageBox,
|
||
ElSwitch,
|
||
ElTag,
|
||
ElRow,
|
||
ElCol,
|
||
ElSteps,
|
||
ElStep,
|
||
ElEmpty
|
||
} from 'element-plus'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import type {
|
||
ShopSeriesGrantResponse,
|
||
PackageSeriesResponse,
|
||
ShopResponse,
|
||
PackageResponse,
|
||
CommissionTier
|
||
} from '@/types/api'
|
||
import type { SearchFormItem } from '@/types'
|
||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||
import { useAuth } from '@/composables/useAuth'
|
||
import { useUserStore } from '@/store/modules/user'
|
||
import { storeToRefs } from 'pinia'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import {
|
||
CommonStatus,
|
||
getStatusText,
|
||
frontendStatusToApi,
|
||
apiStatusToFrontend,
|
||
STATUS_SELECT_OPTIONS,
|
||
getEnableStatusText
|
||
} from '@/config/constants'
|
||
|
||
defineOptions({ name: 'SeriesGrants' })
|
||
|
||
const { hasAuth } = useAuth()
|
||
const canUpdateSeriesGrantStatus = hasAuth('series_grants:update_status')
|
||
const router = useRouter()
|
||
const userStore = useUserStore()
|
||
const { info: userInfo } = storeToRefs(userStore)
|
||
const isAgentAccount = computed(() => userInfo.value.user_type === 3)
|
||
|
||
const dialogVisible = ref(false)
|
||
const loading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const seriesLoading = ref(false)
|
||
const shopLoading = ref(false)
|
||
const packageLoading = ref(false)
|
||
const tableRef = ref()
|
||
const formRef = ref<FormInstance>()
|
||
const currentStep = ref(0) // 当前步骤,0 为第一步,1 为第二步
|
||
|
||
const seriesOptions = ref<PackageSeriesResponse[]>([])
|
||
const shopOptions = ref<ShopResponse[]>([])
|
||
const shopTreeData = ref<ShopResponse[]>([])
|
||
const packageOptions = ref<PackageResponse[]>([])
|
||
const selectedPackageIds = ref<number[]>([])
|
||
const searchSeriesOptions = ref<PackageSeriesResponse[]>([])
|
||
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,
|
||
exclude_self: true
|
||
} as any)
|
||
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,
|
||
series_id: undefined as number | undefined,
|
||
allocator_shop_id: undefined as number | undefined,
|
||
status: undefined as number | undefined
|
||
}
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive({ ...initialSearchState })
|
||
|
||
// 搜索表单配置
|
||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||
{
|
||
label: '被分配店铺',
|
||
prop: 'shop_id',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
filterable: true,
|
||
remote: true,
|
||
remoteMethod: handleSearchShop,
|
||
loading: shopLoading.value,
|
||
placeholder: '请选择或搜索被分配的店铺'
|
||
},
|
||
options: () =>
|
||
searchShopOptions.value.map((s) => ({
|
||
label: s.shop_name,
|
||
value: s.id
|
||
}))
|
||
},
|
||
|
||
{
|
||
label: '分配者店铺',
|
||
prop: 'allocator_shop_id',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
filterable: true,
|
||
remote: true,
|
||
remoteMethod: handleSearchAllocatorShop,
|
||
loading: shopLoading.value,
|
||
placeholder: '请选择或搜索分配者店铺'
|
||
},
|
||
options: () =>
|
||
searchAllocatorShopOptions.value.map((s) => ({
|
||
label: s.shop_name,
|
||
value: s.id
|
||
}))
|
||
},
|
||
|
||
{
|
||
label: '套餐系列',
|
||
prop: 'series_id',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
filterable: true,
|
||
remote: true,
|
||
remoteMethod: handleSearchSeries,
|
||
loading: seriesLoading.value,
|
||
placeholder: '请选择或搜索套餐系列'
|
||
},
|
||
options: () =>
|
||
searchSeriesOptions.value.map((s) => ({
|
||
label: s.series_name,
|
||
value: s.id
|
||
}))
|
||
},
|
||
|
||
{
|
||
label: '状态',
|
||
prop: 'status',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请选择状态'
|
||
},
|
||
options: STATUS_SELECT_OPTIONS
|
||
}
|
||
])
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
page: 1,
|
||
page_size: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: '系列名称', prop: 'series_name' },
|
||
{ label: '店铺名称', prop: 'shop_name' },
|
||
{ label: '分配者店铺', prop: 'allocator_shop_name' },
|
||
{ label: '佣金类型', prop: 'commission_type' },
|
||
{ label: '一次性佣金金额', prop: 'one_time_commission_amount' },
|
||
{ label: '强制充值', prop: 'force_recharge_enabled' },
|
||
{ label: '套餐数量', prop: 'package_count' },
|
||
...(canUpdateSeriesGrantStatus ? [{ label: '状态', prop: 'status' }] : []),
|
||
{ label: '创建时间', prop: 'created_at' }
|
||
]
|
||
|
||
// 表单数据
|
||
const form = reactive<any>({
|
||
id: 0,
|
||
series_id: undefined,
|
||
shop_id: undefined,
|
||
series_name: '',
|
||
shop_name: '',
|
||
allocator_shop_name: '',
|
||
enable_one_time_commission: false, // 是否启用一次性佣金(从系列继承)
|
||
commission_type: 'fixed' as 'fixed' | 'tiered', // 佣金类型
|
||
one_time_commission_amount: undefined, // 固定佣金金额(元)
|
||
series_max_commission_amount: 0, // 系列最大佣金金额(元)
|
||
commission_tiers: [] as Array<{
|
||
operator?: '>=' | '>' | '<=' | '<' // 比较运算符
|
||
threshold: number // 达标阈值
|
||
dimension: 'sales_count' | 'sales_amount' // 统计维度
|
||
stat_scope?: 'self' | 'self_and_sub' // 统计范围
|
||
amount: number // 佣金金额(元)- 这是唯一可编辑的字段
|
||
max_amount?: number // 系列配置的最大佣金金额(元)- 用于验证
|
||
}>, // 梯度配置(包含完整字段,从系列继承)
|
||
enable_force_recharge: false,
|
||
force_recharge_amount: undefined,
|
||
force_recharge_locked: false, // 强充是否被套餐系列锁定
|
||
series_force_recharge_amount: 0, // 系列强充金额(元)
|
||
packages: [] as Array<{
|
||
package_id: number
|
||
cost_price: number
|
||
original_cost_price?: number
|
||
suggested_retail_price?: number
|
||
}> // 套餐配置
|
||
})
|
||
|
||
// 动态验证规则
|
||
const rules = computed<FormRules>(() => {
|
||
const baseRules: FormRules = {
|
||
series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }],
|
||
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }]
|
||
}
|
||
|
||
// 只有启用一次性佣金时才添加佣金验证规则
|
||
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(
|
||
`佣金金额不能超过该系列最大值 ¥${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) {
|
||
callback(
|
||
new Error(
|
||
`第${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}`
|
||
)
|
||
)
|
||
return
|
||
}
|
||
}
|
||
|
||
callback()
|
||
},
|
||
trigger: 'change'
|
||
}
|
||
]
|
||
}
|
||
}
|
||
|
||
// 如果启用了强制充值,添加验证规则
|
||
if (form.enable_force_recharge) {
|
||
baseRules.force_recharge_amount = [
|
||
{ required: true, message: '请输入强制充值金额', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
return baseRules
|
||
})
|
||
|
||
const allocationList = ref<ShopSeriesGrantResponse[]>([])
|
||
const dialogType = ref('add')
|
||
|
||
// 动态列配置
|
||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||
{
|
||
prop: 'series_name',
|
||
label: '系列名称',
|
||
width: 200,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
const hasPermission = hasAuth('series_grants:detail')
|
||
return h(
|
||
'span',
|
||
{
|
||
style: hasPermission
|
||
? 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;'
|
||
: '',
|
||
onClick: hasPermission
|
||
? (e: MouseEvent) => {
|
||
e.stopPropagation()
|
||
handleNameClick(row)
|
||
}
|
||
: undefined
|
||
},
|
||
row.series_name
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'shop_name',
|
||
label: '店铺名称',
|
||
minWidth: 140
|
||
},
|
||
{
|
||
prop: 'allocator_shop_name',
|
||
label: '分配者店铺',
|
||
minWidth: 120,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
// 如果是平台分配(allocator_shop_id为0),显示"平台"标签
|
||
if (row.allocator_shop_id === 0) {
|
||
return h(
|
||
'span',
|
||
{ style: 'color: #409eff; font-weight: bold' },
|
||
row.allocator_shop_name || '平台'
|
||
)
|
||
}
|
||
return row.allocator_shop_name || '-'
|
||
}
|
||
},
|
||
{
|
||
prop: 'commission_type',
|
||
label: '佣金类型',
|
||
width: 110,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
const typeMap = {
|
||
fixed: '固定佣金',
|
||
tiered: '梯度佣金'
|
||
}
|
||
return h(
|
||
ElTag,
|
||
{ type: row.commission_type === 'fixed' ? 'success' : 'warning', size: 'small' },
|
||
() => typeMap[row.commission_type] || '-'
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'one_time_commission_amount',
|
||
label: '一次性佣金金额',
|
||
width: 140,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
if (row.commission_type === 'fixed' && row.one_time_commission_amount > 0) {
|
||
return h(
|
||
'span',
|
||
{ style: 'color: #f56c6c; font-weight: bold' },
|
||
`¥${(row.one_time_commission_amount / 100).toFixed(2)}`
|
||
)
|
||
}
|
||
return '-'
|
||
}
|
||
},
|
||
{
|
||
prop: 'force_recharge_enabled',
|
||
label: '强制充值',
|
||
width: 100,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
return h(
|
||
ElTag,
|
||
{ type: row.force_recharge_enabled ? 'warning' : 'info', size: 'small' },
|
||
() => getEnableStatusText(row.force_recharge_enabled || false)
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'force_recharge_locked',
|
||
label: '强充锁定',
|
||
width: 100,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
return h(
|
||
ElTag,
|
||
{ type: row.force_recharge_locked ? 'danger' : 'success', size: 'small' },
|
||
() => (row.force_recharge_locked ? '是' : '否')
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'force_recharge_amount',
|
||
label: '强充金额',
|
||
width: 100,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
if (!row.force_recharge_enabled) return '-'
|
||
return `¥${((row.force_recharge_amount || 0) / 100).toFixed(2)}`
|
||
}
|
||
},
|
||
{
|
||
prop: 'package_count',
|
||
label: '套餐数量',
|
||
width: 100
|
||
},
|
||
...(canUpdateSeriesGrantStatus
|
||
? [
|
||
{
|
||
prop: 'status',
|
||
label: '状态',
|
||
width: 100,
|
||
formatter: (row: ShopSeriesGrantResponse) => {
|
||
const frontendStatus = apiStatusToFrontend(row.status)
|
||
return h(ElSwitch, {
|
||
modelValue: frontendStatus,
|
||
activeValue: CommonStatus.ENABLED,
|
||
inactiveValue: CommonStatus.DISABLED,
|
||
activeText: getStatusText(CommonStatus.ENABLED),
|
||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||
inlinePrompt: true,
|
||
'onUpdate:modelValue': (val: string | number | boolean) =>
|
||
handleStatusChange(row, val as number)
|
||
})
|
||
}
|
||
}
|
||
]
|
||
: []),
|
||
{
|
||
prop: 'created_at',
|
||
label: '创建时间',
|
||
width: 180,
|
||
formatter: (row: ShopSeriesGrantResponse) => formatDateTime(row.created_at)
|
||
}
|
||
])
|
||
|
||
// 操作列配置
|
||
const getActions = (row: ShopSeriesGrantResponse) => {
|
||
const actions: any[] = []
|
||
|
||
if (hasAuth('series_grants:packages_list')) {
|
||
actions.push({
|
||
label: '套餐列表',
|
||
handler: () => router.push({ name: 'SeriesGrantPackages', params: { id: row.id } }),
|
||
type: 'primary'
|
||
})
|
||
}
|
||
|
||
if (hasAuth('series_grants:edit')) {
|
||
actions.push({ label: '编辑', handler: () => showDialog('edit', row), type: 'primary' })
|
||
}
|
||
|
||
if (hasAuth('series_grants:delete')) {
|
||
actions.push({ label: '删除', handler: () => deleteAllocation(row), type: 'danger' })
|
||
}
|
||
|
||
return actions
|
||
}
|
||
|
||
// 构建树形结构数据
|
||
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 filterShopNode = (value: string, data: any) => {
|
||
if (!value) return true
|
||
return data.shop_name.toLowerCase().includes(value.toLowerCase())
|
||
}
|
||
|
||
onMounted(() => {
|
||
getTableData()
|
||
})
|
||
|
||
let isFirstActivation = true
|
||
onActivated(() => {
|
||
if (!isFirstActivation) {
|
||
getTableData()
|
||
}
|
||
isFirstActivation = false
|
||
})
|
||
|
||
// 加载套餐选项(用于新增对话框)
|
||
const loadPackageOptions = async (packageName?: string) => {
|
||
packageLoading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: 1,
|
||
page_size: 20
|
||
}
|
||
|
||
// 如果已选择套餐系列,则根据系列ID过滤套餐
|
||
if (form.series_id) {
|
||
params.series_id = form.series_id
|
||
}
|
||
|
||
if (packageName) {
|
||
params.package_name = packageName
|
||
}
|
||
|
||
const res = await PackageManageService.getPackages(params)
|
||
if (res.code === 0) {
|
||
// 过滤掉赠送套餐
|
||
packageOptions.value = res.data.items.filter((pkg) => !pkg.is_gift)
|
||
}
|
||
} catch (error) {
|
||
console.error('加载套餐选项失败:', error)
|
||
} finally {
|
||
packageLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索套餐
|
||
const searchPackages = (query: string) => {
|
||
if (query) {
|
||
loadPackageOptions(query)
|
||
} else {
|
||
loadPackageOptions()
|
||
}
|
||
}
|
||
|
||
// 移除套餐
|
||
const removePackage = (index: number) => {
|
||
form.packages.splice(index, 1)
|
||
// 同步更新选中的套餐ID
|
||
const removedPackageId = form.packages[index]?.package_id
|
||
if (removedPackageId) {
|
||
const idIndex = selectedPackageIds.value.indexOf(removedPackageId)
|
||
if (idIndex > -1) {
|
||
selectedPackageIds.value.splice(idIndex, 1)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取套餐名称
|
||
const getPackageName = (packageId: number) => {
|
||
const pkg = packageOptions.value.find((p) => p.id === packageId)
|
||
return pkg ? pkg.package_name : `套餐ID: ${packageId}`
|
||
}
|
||
|
||
const getPackageCostPriceMax = (pkg?: {
|
||
original_cost_price?: number
|
||
suggested_retail_price?: number | null
|
||
}) => {
|
||
if (pkg?.suggested_retail_price !== undefined && pkg?.suggested_retail_price !== null) {
|
||
return Number((pkg.suggested_retail_price * 1.5).toFixed(2))
|
||
}
|
||
if (pkg?.original_cost_price !== undefined && pkg.original_cost_price !== null) {
|
||
return Number((pkg.original_cost_price * 1.5).toFixed(2))
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
const formatSuggestedRetailPrice = (suggestedRetailPrice?: number | null) => {
|
||
if (suggestedRetailPrice === undefined || suggestedRetailPrice === null) {
|
||
return '-'
|
||
}
|
||
return `¥${suggestedRetailPrice.toFixed(2)}`
|
||
}
|
||
|
||
// 监听套餐选择变化
|
||
watch(selectedPackageIds, (newIds, oldIds) => {
|
||
// 找出新增的ID
|
||
const addedIds = newIds.filter((id) => !oldIds?.includes(id))
|
||
// 找出删除的ID
|
||
const removedIds = oldIds?.filter((id) => !newIds.includes(id)) || []
|
||
|
||
// 添加新选中的套餐
|
||
addedIds.forEach((id) => {
|
||
if (!form.packages.find((p: any) => p.package_id === id)) {
|
||
// 从 packageOptions 中查找套餐,获取其成本价
|
||
const pkg = packageOptions.value.find((p) => p.id === id)
|
||
const costPrice = pkg?.cost_price ? pkg.cost_price / 100 : 0 // 将分转换为元
|
||
form.packages.push({
|
||
package_id: id,
|
||
cost_price: costPrice,
|
||
original_cost_price: costPrice, // 保存原始成本价用于验证
|
||
suggested_retail_price:
|
||
pkg?.suggested_retail_price !== undefined && pkg?.suggested_retail_price !== null
|
||
? pkg.suggested_retail_price / 100
|
||
: undefined
|
||
})
|
||
}
|
||
})
|
||
|
||
// 删除取消选中的套餐
|
||
removedIds.forEach((id) => {
|
||
const index = form.packages.findIndex((p: any) => p.package_id === id)
|
||
if (index > -1) {
|
||
form.packages.splice(index, 1)
|
||
}
|
||
})
|
||
})
|
||
|
||
// 监听套餐系列选择变化,重新加载套餐列表并获取佣金类型
|
||
watch(
|
||
() => form.series_id,
|
||
async (newSeriesId, oldSeriesId) => {
|
||
// 只在新增模式且系列ID发生变化时处理
|
||
if (dialogType.value === 'add' && newSeriesId !== oldSeriesId) {
|
||
// 清空已选套餐
|
||
form.packages = []
|
||
selectedPackageIds.value = []
|
||
packageOptions.value = []
|
||
|
||
// 如果选择了系列
|
||
if (newSeriesId) {
|
||
// 获取系列详情,读取佣金配置
|
||
const selectedSeries = seriesOptions.value.find((s) => s.id === newSeriesId)
|
||
if (selectedSeries) {
|
||
// 设置系列名称
|
||
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) {
|
||
form.commission_type = commissionConfig.commission_type
|
||
|
||
// 根据佣金类型初始化对应字段
|
||
if (commissionConfig.commission_type === 'fixed') {
|
||
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 = undefined
|
||
form.series_max_commission_amount = 0
|
||
// 梯度配置从系列继承,包含完整字段,佣金金额也默认继承
|
||
if (commissionConfig.tiers && commissionConfig.tiers.length > 0) {
|
||
form.commission_tiers = commissionConfig.tiers.map((tier) => ({
|
||
operator: tier.operator || '>=', // 比较运算符(只读)
|
||
threshold: tier.threshold, // 达标阈值(只读)
|
||
dimension: tier.dimension, // 统计维度(只读)
|
||
stat_scope: tier.stat_scope, // 统计范围(只读)
|
||
amount: tier.amount / 100, // 佣金金额(元)- 默认显示系列的值,可编辑
|
||
max_amount: tier.amount / 100 // 系列配置的最大佣金金额(元)- 用于验证
|
||
}))
|
||
} else {
|
||
form.commission_tiers = []
|
||
}
|
||
}
|
||
|
||
// 设置系列强充金额(从分转换为元)
|
||
if (commissionConfig.force_amount) {
|
||
form.series_force_recharge_amount = commissionConfig.force_amount / 100
|
||
} else {
|
||
form.series_force_recharge_amount = 0
|
||
}
|
||
} else {
|
||
// 默认固定佣金
|
||
form.commission_type = 'fixed'
|
||
form.one_time_commission_amount = undefined
|
||
form.series_max_commission_amount = 0
|
||
form.series_force_recharge_amount = 0
|
||
}
|
||
}
|
||
|
||
// 重新加载该系列下的套餐
|
||
loadPackageOptions()
|
||
} else {
|
||
// 未选择系列,重置佣金类型
|
||
form.series_name = ''
|
||
form.commission_type = 'fixed'
|
||
form.one_time_commission_amount = undefined
|
||
form.series_max_commission_amount = 0
|
||
form.series_force_recharge_amount = 0
|
||
form.commission_tiers = []
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
// 监听启用强制充值开关,自动设置默认强充金额
|
||
watch(
|
||
() => form.enable_force_recharge,
|
||
(enabled) => {
|
||
if (enabled && form.series_force_recharge_amount > 0 && !form.force_recharge_amount) {
|
||
// 启用强充时,如果系列有强充金额且用户未输入,则使用系列的强充金额作为默认值
|
||
form.force_recharge_amount = form.series_force_recharge_amount
|
||
}
|
||
}
|
||
)
|
||
|
||
// 加载系列选项(用于新增对话框,默认加载10条)
|
||
const loadSeriesOptions = async (seriesName?: string) => {
|
||
seriesLoading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: 1,
|
||
page_size: 10,
|
||
status: 1
|
||
}
|
||
if (seriesName) {
|
||
params.series_name = seriesName
|
||
}
|
||
const res = await PackageSeriesService.getPackageSeries(params)
|
||
if (res.code === 0) {
|
||
seriesOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('加载系列选项失败:', error)
|
||
} finally {
|
||
seriesLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载搜索栏系列选项(默认加载10条)
|
||
const loadSearchSeriesOptions = async () => {
|
||
try {
|
||
const res = await PackageSeriesService.getPackageSeries({
|
||
page: 1,
|
||
page_size: 10,
|
||
status: 1
|
||
})
|
||
if (res.code === 0) {
|
||
searchSeriesOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('加载搜索栏系列选项失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载搜索栏店铺选项(默认加载10条)
|
||
const loadSearchShopOptions = async () => {
|
||
try {
|
||
const res = await ShopService.getShops({ page: 1, page_size: 10 })
|
||
if (res.code === 0) {
|
||
searchShopOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('加载搜索栏店铺选项失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载搜索栏分配者店铺选项(默认加载10条)
|
||
const loadSearchAllocatorShopOptions = async () => {
|
||
try {
|
||
const res = await ShopService.getShops({ page: 1, page_size: 10 })
|
||
if (res.code === 0) {
|
||
searchAllocatorShopOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('加载搜索栏分配者店铺选项失败:', error)
|
||
}
|
||
}
|
||
|
||
// 搜索系列(用于新增对话框)
|
||
const searchSeries = (query: string) => {
|
||
if (query) {
|
||
loadSeriesOptions(query)
|
||
} else {
|
||
loadSeriesOptions()
|
||
}
|
||
}
|
||
|
||
// 加载店铺列表(用于新增对话框) - 改用级联查询
|
||
const loadShopListForDialog = async () => {
|
||
shopLoading.value = true
|
||
try {
|
||
const res = await ShopService.getShopsCascade({
|
||
parent_id: undefined,
|
||
exclude_self: true
|
||
} as any)
|
||
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) {
|
||
await loadSearchSeriesOptions()
|
||
return
|
||
}
|
||
try {
|
||
const res = await PackageSeriesService.getPackageSeries({
|
||
page: 1,
|
||
page_size: 10,
|
||
series_name: query,
|
||
status: 1
|
||
})
|
||
if (res.code === 0) {
|
||
searchSeriesOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('搜索系列失败:', error)
|
||
}
|
||
}
|
||
|
||
// 搜索店铺(用于搜索栏-被分配的店铺)
|
||
const handleSearchShop = async (query: string) => {
|
||
if (!query) {
|
||
await loadSearchShopOptions()
|
||
return
|
||
}
|
||
try {
|
||
const res = await ShopService.getShops({
|
||
page: 1,
|
||
page_size: 10,
|
||
shop_name: query
|
||
})
|
||
if (res.code === 0) {
|
||
searchShopOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('搜索店铺失败:', error)
|
||
}
|
||
}
|
||
|
||
// 搜索分配者店铺(用于搜索栏)
|
||
const handleSearchAllocatorShop = async (query: string) => {
|
||
if (!query) {
|
||
await loadSearchAllocatorShopOptions()
|
||
return
|
||
}
|
||
try {
|
||
const res = await ShopService.getShops({
|
||
page: 1,
|
||
page_size: 10,
|
||
shop_name: query
|
||
})
|
||
if (res.code === 0) {
|
||
searchAllocatorShopOptions.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('搜索分配者店铺失败:', error)
|
||
}
|
||
}
|
||
|
||
// 获取分配列表
|
||
const getTableData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
page: pagination.page,
|
||
page_size: pagination.page_size,
|
||
shop_id: searchForm.shop_id || undefined,
|
||
series_id: searchForm.series_id || undefined,
|
||
allocator_shop_id:
|
||
searchForm.allocator_shop_id !== undefined ? searchForm.allocator_shop_id : undefined,
|
||
status: searchForm.status || undefined
|
||
}
|
||
const res = await ShopSeriesGrantService.getShopSeriesGrants(params)
|
||
if (res.code === 0) {
|
||
allocationList.value = res.data.items
|
||
pagination.total = res.data.total || 0
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
console.log('获取系列分配列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 重置搜索
|
||
const handleReset = () => {
|
||
Object.assign(searchForm, { ...initialSearchState })
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
// 刷新表格
|
||
const handleRefresh = () => {
|
||
getTableData()
|
||
}
|
||
|
||
// 处理表格分页变化
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.page_size = newPageSize
|
||
getTableData()
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.page = newCurrentPage
|
||
getTableData()
|
||
}
|
||
|
||
// 显示新增/编辑对话框
|
||
const showDialog = async (type: string, row?: ShopSeriesGrantResponse) => {
|
||
dialogVisible.value = true
|
||
dialogType.value = type
|
||
|
||
// 每次打开对话框时重新加载店铺列表,确保获取最新的店铺数据
|
||
await loadShopListForDialog()
|
||
// 加载系列选项
|
||
await loadSeriesOptions()
|
||
|
||
if (type === 'edit' && row) {
|
||
// 编辑模式:先调用详情接口获取完整数据
|
||
loading.value = true
|
||
try {
|
||
const res = await ShopSeriesGrantService.getShopSeriesGrantDetail(row.id)
|
||
if (res.code === 0) {
|
||
const detail = res.data
|
||
|
||
// 设置基本信息
|
||
form.id = detail.id
|
||
form.series_id = detail.series_id
|
||
form.shop_id = detail.shop_id
|
||
form.series_name = detail.series_name
|
||
form.shop_name = detail.shop_name
|
||
form.allocator_shop_name = detail.allocator_shop_name
|
||
form.commission_type = detail.commission_type
|
||
|
||
// 获取系列配置信息
|
||
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') {
|
||
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 =
|
||
selectedSeries.one_time_commission_config.commission_amount / 100
|
||
} else {
|
||
form.series_max_commission_amount = 0
|
||
}
|
||
} else if (detail.commission_type === 'tiered' && detail.commission_tiers) {
|
||
// 梯度配置:从响应中获取完整数据
|
||
// 获取系列配置以获取 dimension 和 stat_scope
|
||
const selectedSeries = seriesOptions.value.find((s) => s.id === detail.series_id)
|
||
const seriesTiers = selectedSeries?.one_time_commission_config?.tiers || []
|
||
|
||
form.commission_tiers = detail.commission_tiers.map((tier, index) => {
|
||
const seriesTier = seriesTiers[index]
|
||
return {
|
||
operator: tier.operator || seriesTier?.operator || '>=', // 比较运算符(只读,优先使用授权的,否则从系列获取)
|
||
threshold: tier.threshold, // 达标阈值(只读)
|
||
dimension: seriesTier?.dimension || 'sales_count', // 统计维度(只读,从系列获取)
|
||
stat_scope: seriesTier?.stat_scope, // 统计范围(只读,从系列获取)
|
||
amount: tier.amount / 100, // 佣金金额(元)- 可编辑,将分转换为元
|
||
max_amount: seriesTier?.amount ? seriesTier.amount / 100 : undefined // 系列配置的最大佣金金额(元)- 用于验证
|
||
}
|
||
})
|
||
form.series_max_commission_amount = 0
|
||
}
|
||
|
||
// 设置系列强充金额
|
||
if (selectedSeries?.one_time_commission_config?.force_amount) {
|
||
form.series_force_recharge_amount =
|
||
selectedSeries.one_time_commission_config.force_amount / 100
|
||
} else {
|
||
form.series_force_recharge_amount = 0
|
||
}
|
||
|
||
// 设置强制充值配置
|
||
form.enable_force_recharge = detail.force_recharge_enabled
|
||
form.force_recharge_amount = detail.force_recharge_amount
|
||
? detail.force_recharge_amount / 100
|
||
: undefined
|
||
form.force_recharge_locked = detail.force_recharge_locked || false
|
||
|
||
// 设置套餐数据
|
||
if (detail.packages && detail.packages.length > 0) {
|
||
// 加载该系列下的套餐选项,以便获取原始成本价
|
||
if (detail.series_id) {
|
||
await loadPackageOptions()
|
||
}
|
||
|
||
form.packages = detail.packages.map((pkg) => {
|
||
// 从 packageOptions 中查找套餐以获取原始成本价
|
||
const pkgOption = packageOptions.value.find((p) => p.id === pkg.package_id)
|
||
const originalCostPrice = pkgOption?.cost_price ? pkgOption.cost_price / 100 : 0
|
||
|
||
return {
|
||
package_id: pkg.package_id,
|
||
cost_price: pkg.cost_price / 100, // 将分转换为元
|
||
original_cost_price: originalCostPrice, // 保存原始成本价用于验证
|
||
suggested_retail_price:
|
||
pkgOption?.suggested_retail_price !== undefined &&
|
||
pkgOption?.suggested_retail_price !== null
|
||
? pkgOption.suggested_retail_price / 100
|
||
: undefined
|
||
}
|
||
})
|
||
selectedPackageIds.value = detail.packages.map((pkg) => pkg.package_id)
|
||
} else {
|
||
form.packages = []
|
||
selectedPackageIds.value = []
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取详情失败:', error)
|
||
console.log('获取详情失败')
|
||
dialogVisible.value = false
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
} else {
|
||
// 新增模式:重置表单
|
||
form.id = 0
|
||
form.series_id = undefined
|
||
form.shop_id = undefined
|
||
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 = undefined
|
||
form.series_max_commission_amount = 0
|
||
form.series_force_recharge_amount = 0
|
||
form.commission_tiers = []
|
||
form.enable_force_recharge = false
|
||
form.force_recharge_amount = undefined
|
||
form.force_recharge_locked = false
|
||
form.packages = []
|
||
selectedPackageIds.value = []
|
||
packageOptions.value = [] // 清空套餐选项
|
||
}
|
||
|
||
// 重置表单验证状态
|
||
nextTick(() => {
|
||
formRef.value?.clearValidate()
|
||
})
|
||
}
|
||
|
||
// 处理弹窗关闭事件
|
||
const handleDialogClosed = () => {
|
||
// 清除表单验证状态
|
||
formRef.value?.clearValidate()
|
||
// 重置步骤
|
||
currentStep.value = 0
|
||
// 重置表单数据
|
||
form.id = 0
|
||
form.series_id = undefined
|
||
form.shop_id = undefined
|
||
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 = undefined
|
||
form.series_max_commission_amount = 0
|
||
form.series_force_recharge_amount = 0
|
||
form.commission_tiers = []
|
||
form.enable_force_recharge = false
|
||
form.force_recharge_amount = undefined
|
||
form.force_recharge_locked = false
|
||
form.packages = []
|
||
selectedPackageIds.value = []
|
||
}
|
||
|
||
// 查看详情
|
||
const handleViewDetail = (row: ShopSeriesGrantResponse) => {
|
||
router.push(`/package-management/series-grants/detail/${row.id}`)
|
||
}
|
||
|
||
// 处理名称点击
|
||
const handleNameClick = (row: ShopSeriesGrantResponse) => {
|
||
if (hasAuth('series_grants:detail')) {
|
||
handleViewDetail(row)
|
||
} else {
|
||
ElMessage.warning('您没有查看详情的权限')
|
||
}
|
||
}
|
||
|
||
// 删除分配
|
||
const deleteAllocation = (row: ShopSeriesGrantResponse) => {
|
||
ElMessageBox.confirm(
|
||
`确定删除系列 ${row.series_name} 对店铺 ${row.shop_name} 的授权吗?`,
|
||
'删除确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'error'
|
||
}
|
||
)
|
||
.then(async () => {
|
||
try {
|
||
await ShopSeriesGrantService.deleteShopSeriesGrant(row.id)
|
||
ElMessage.success('删除成功')
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error(error)
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消删除
|
||
})
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||
if (!formEl) return
|
||
|
||
await formEl.validate(async (valid) => {
|
||
if (valid) {
|
||
submitLoading.value = true
|
||
try {
|
||
// 构建请求数据
|
||
const data: any = {}
|
||
|
||
// 只有启用一次性佣金时才传递佣金相关参数
|
||
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) // 将元转换为分
|
||
}))
|
||
}
|
||
}
|
||
|
||
// 强制充值配置(无论开关状态都要传递)
|
||
data.enable_force_recharge = form.enable_force_recharge
|
||
if (form.enable_force_recharge) {
|
||
// 启用时传递强充金额(分)
|
||
data.force_recharge_amount = Math.round((form.force_recharge_amount || 0) * 100)
|
||
} else {
|
||
// 关闭时传递 0
|
||
data.force_recharge_amount = 0
|
||
}
|
||
|
||
// 可选:套餐配置(将元转换为分)
|
||
if (form.packages.length > 0) {
|
||
// 验证是否包含赠送套餐
|
||
for (const pkg of form.packages) {
|
||
const pkgInfo = packageOptions.value.find((p) => p.id === pkg.package_id)
|
||
if (pkgInfo?.is_gift) {
|
||
ElMessage.error(`套餐 ${getPackageName(pkg.package_id)} 是赠送套餐,不能参与授权`)
|
||
submitLoading.value = false
|
||
return
|
||
}
|
||
}
|
||
|
||
// 验证成本价是否在允许范围内
|
||
for (const pkg of form.packages) {
|
||
const costPrice = pkg.cost_price ?? 0
|
||
const originalCostPrice = pkg.original_cost_price ?? 0
|
||
const maxCostPrice = getPackageCostPriceMax(pkg)
|
||
|
||
if (maxCostPrice !== undefined && costPrice > maxCostPrice) {
|
||
ElMessage.error(
|
||
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能高于当前上限(最高:¥${maxCostPrice.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 ?? 0) * 100) // 将元转换为分
|
||
}))
|
||
}
|
||
|
||
if (dialogType.value === 'add') {
|
||
// 新增时需要必填字段
|
||
data.series_id = form.series_id
|
||
data.shop_id = form.shop_id
|
||
await ShopSeriesGrantService.createShopSeriesGrant(data)
|
||
ElMessage.success('新增成功')
|
||
} else {
|
||
// 编辑时调用更新接口
|
||
await ShopSeriesGrantService.updateShopSeriesGrant(form.id, data)
|
||
ElMessage.success('修改成功')
|
||
}
|
||
|
||
dialogVisible.value = false
|
||
formEl.resetFields()
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 状态切换
|
||
const handleStatusChange = async (row: ShopSeriesGrantResponse, newFrontendStatus: number) => {
|
||
const oldStatus = row.status
|
||
const newApiStatus = frontendStatusToApi(newFrontendStatus)
|
||
row.status = newApiStatus
|
||
try {
|
||
await ShopSeriesGrantService.updateShopSeriesGrant(row.id, { status: newApiStatus } as any)
|
||
ElMessage.success('状态切换成功')
|
||
} catch (error) {
|
||
row.status = oldStatus
|
||
console.error(error)
|
||
}
|
||
}
|
||
|
||
// 下一步
|
||
const handleNextStep = async () => {
|
||
if (!formRef.value) return
|
||
|
||
// 根据当前步骤验证不同的字段
|
||
let fieldsToValidate: string[] = []
|
||
|
||
if (currentStep.value === 0) {
|
||
// 第一步:验证系列和店铺选择
|
||
fieldsToValidate = ['series_id', 'shop_id']
|
||
}
|
||
|
||
try {
|
||
// 验证当前步骤的字段
|
||
await formRef.value.validateField(fieldsToValidate)
|
||
// 验证通过,进入下一步
|
||
currentStep.value++
|
||
} catch (error) {
|
||
console.error('表单验证失败:', error)
|
||
}
|
||
}
|
||
|
||
// 上一步
|
||
const handlePrevStep = () => {
|
||
if (currentStep.value > 0) {
|
||
currentStep.value--
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.series-grants-page {
|
||
// 可以添加特定样式
|
||
}
|
||
|
||
:deep(.el-table__row.table-row-with-context-menu) {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.dialog-footer {
|
||
text-align: right;
|
||
}
|
||
|
||
.form-tip {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
|
||
.max-amount-hint {
|
||
color: var(--el-text-color-regular);
|
||
|
||
.amount-value {
|
||
font-weight: 600;
|
||
color: var(--el-color-warning);
|
||
}
|
||
}
|
||
|
||
.series-force-hint {
|
||
color: var(--el-text-color-regular);
|
||
|
||
.amount-value {
|
||
font-weight: 600;
|
||
color: var(--el-color-primary);
|
||
}
|
||
}
|
||
}
|
||
|
||
.form-section-title {
|
||
padding-bottom: 8px;
|
||
margin: 20px 0 16px;
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
|
||
.title-text {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
|
||
.commission-type-display {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
|
||
.type-hint {
|
||
font-size: 13px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
}
|
||
|
||
.tier-list {
|
||
width: 100%;
|
||
}
|
||
|
||
.tier-item {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
|
||
.tier-label {
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.package-list {
|
||
width: 100%;
|
||
}
|
||
|
||
.package-item {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
padding: 8px;
|
||
margin-bottom: 8px;
|
||
background-color: var(--el-fill-color-light);
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
border-radius: 4px;
|
||
transition:
|
||
background-color 0.3s,
|
||
border-color 0.3s;
|
||
|
||
&:hover {
|
||
background-color: var(--el-fill-color);
|
||
}
|
||
|
||
.package-name {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.cost-price-input-wrapper {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
|
||
.min-cost-hint {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
gap: 20px;
|
||
padding: 12px;
|
||
margin-bottom: 18px;
|
||
background-color: var(--el-fill-color-lighter);
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
flex: 1;
|
||
align-items: center;
|
||
}
|
||
|
||
.info-label {
|
||
margin-right: 8px;
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--el-color-primary);
|
||
}
|
||
|
||
.readonly-value {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
</style>
|