291 lines
8.6 KiB
Vue
291 lines
8.6 KiB
Vue
<template>
|
|
<ElDialog
|
|
v-model="dialogVisible"
|
|
:title="props.dialogType === 'add' ? '添加套餐' : '编辑套餐'"
|
|
width="40%"
|
|
:close-on-click-modal="false"
|
|
@closed="emit('closed')"
|
|
>
|
|
<ElForm ref="formRef" :model="props.form" :rules="props.rules" label-width="130px">
|
|
<ElFormItem v-if="props.dialogType === 'add'" label="选择套餐" prop="package_ids">
|
|
<ElSelect
|
|
:model-value="props.form.package_ids"
|
|
@update:model-value="updateForm({ package_ids: $event })"
|
|
placeholder="请选择套餐(可多选)"
|
|
style="width: 100%"
|
|
filterable
|
|
remote
|
|
:remote-method="(query: string) => emit('search', query)"
|
|
:loading="props.packageLoading"
|
|
clearable
|
|
multiple
|
|
:multiple-limit="100"
|
|
popper-class="package-select-dropdown"
|
|
>
|
|
<template
|
|
v-if="props.availablePackages.length === 0 && !props.packageLoading && props.hasSeries"
|
|
>
|
|
<ElOption disabled value="" label="该系列没有可选套餐" />
|
|
</template>
|
|
<ElOption
|
|
v-for="pkg in props.availablePackages"
|
|
:key="pkg.id"
|
|
:label="getPackageOptionLabel(pkg)"
|
|
:value="pkg.id"
|
|
:disabled="pkg.is_authorized"
|
|
/>
|
|
</ElSelect>
|
|
</ElFormItem>
|
|
|
|
<ElFormItem
|
|
v-if="props.dialogType === 'add' && props.form.packages.length"
|
|
label="套餐配置"
|
|
prop="packages"
|
|
>
|
|
<div class="package-config-list">
|
|
<div v-for="pkg in props.form.packages" :key="pkg.package_id" class="package-config-item">
|
|
<div class="package-config-name" :title="pkg.package_name || ''">
|
|
{{ pkg.package_name || '套餐名称不可用' }}
|
|
</div>
|
|
<div class="package-config-cost">
|
|
<span class="package-config-field-label">套餐成本价(元)</span>
|
|
<ElInputNumber
|
|
:model-value="pkg.cost_price_yuan"
|
|
:min="pkg.original_cost_price || 0"
|
|
:max="getPackageCostPriceMax(pkg)"
|
|
:precision="2"
|
|
:step="0.01"
|
|
:controls="false"
|
|
placeholder="请输入成本价"
|
|
style="width: 150px"
|
|
@update:model-value="updatePackageCost(pkg.package_id, $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ElFormItem>
|
|
|
|
<ElFormItem v-if="props.dialogType === 'edit'" label="套餐名称">
|
|
<span>{{ props.form.package_name }}</span>
|
|
</ElFormItem>
|
|
<ElFormItem v-if="props.dialogType === 'edit'" label="套餐编码">
|
|
<span>{{ props.form.package_code }}</span>
|
|
</ElFormItem>
|
|
|
|
<ElFormItem v-if="props.dialogType === 'edit'" label="成本价(元)" prop="cost_price_yuan">
|
|
<ElInputNumber
|
|
:model-value="props.form.cost_price_yuan"
|
|
:precision="2"
|
|
:step="0.01"
|
|
:controls="false"
|
|
style="width: 100%"
|
|
placeholder="请输入成本价"
|
|
@update:model-value="updateForm({ cost_price_yuan: $event ?? 0 })"
|
|
/>
|
|
<div v-if="props.form.original_cost_price" class="form-tip">
|
|
请参考{{ props.form.package_name || '套餐' }} - 套餐成本价: ¥{{
|
|
props.form.original_cost_price.toFixed(2)
|
|
}}
|
|
</div>
|
|
</ElFormItem>
|
|
|
|
<ElFormItem v-if="props.dialogType === 'add' || props.canUpdateExpiryBase" label="生效条件">
|
|
<ElSelect
|
|
:model-value="props.form.expiry_base_override"
|
|
placeholder="请选择生效条件"
|
|
style="width: 100%"
|
|
@update:model-value="updateForm({ expiry_base_override: $event })"
|
|
>
|
|
<ElOption label="跟随套餐默认" value="default" />
|
|
<ElOption label="购买即生效" value="from_purchase" />
|
|
<ElOption label="实名激活时生效" value="from_activation" />
|
|
</ElSelect>
|
|
<div class="form-tip">仅影响后续新订单,不影响已购买套餐</div>
|
|
</ElFormItem>
|
|
|
|
<template v-if="props.dialogType === 'edit'">
|
|
<ElFormItem label="套餐默认生效条件">
|
|
{{ props.form.default_expiry_base_name || '-' }}
|
|
</ElFormItem>
|
|
<ElFormItem label="覆盖生效条件">
|
|
{{ props.form.expiry_base_override_name || '跟随套餐默认' }}
|
|
</ElFormItem>
|
|
<ElFormItem label="最终生效条件">
|
|
{{ props.form.effective_expiry_base_name || '-' }}
|
|
</ElFormItem>
|
|
</template>
|
|
</ElForm>
|
|
|
|
<template #footer>
|
|
<div class="dialog-footer">
|
|
<ElButton @click="dialogVisible = false">取消</ElButton>
|
|
<ElButton type="primary" :loading="props.submitLoading" @click="handleSubmit">
|
|
保存
|
|
</ElButton>
|
|
</div>
|
|
</template>
|
|
</ElDialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, nextTick, ref, watch } from 'vue'
|
|
import {
|
|
ElButton,
|
|
ElDialog,
|
|
ElForm,
|
|
ElFormItem,
|
|
ElInputNumber,
|
|
ElOption,
|
|
ElSelect,
|
|
type FormInstance,
|
|
type FormRules
|
|
} from 'element-plus'
|
|
import type {
|
|
GrantPackageCandidate,
|
|
SeriesGrantPackageForm
|
|
} from '@/utils/business/seriesGrantPackage'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
modelValue: boolean
|
|
dialogType: 'add' | 'edit'
|
|
form: SeriesGrantPackageForm
|
|
rules: FormRules
|
|
availablePackages: GrantPackageCandidate[]
|
|
packageLoading?: boolean
|
|
submitLoading?: boolean
|
|
canUpdateExpiryBase?: boolean
|
|
hasSeries?: boolean
|
|
}>(),
|
|
{
|
|
packageLoading: false,
|
|
submitLoading: false,
|
|
canUpdateExpiryBase: false,
|
|
hasSeries: false
|
|
}
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
'update:form': [form: SeriesGrantPackageForm]
|
|
search: [query: string]
|
|
submit: []
|
|
closed: []
|
|
}>()
|
|
|
|
const formRef = ref<FormInstance>()
|
|
const dialogVisible = computed({
|
|
get: () => props.modelValue,
|
|
set: (value: boolean) => emit('update:modelValue', value)
|
|
})
|
|
|
|
const formatPackageOptionPrice = (price?: number | null) => {
|
|
if (price === undefined || price === null) return '未配置'
|
|
return `¥${(price / 100).toFixed(2)}`
|
|
}
|
|
|
|
const getPackageOptionLabel = (pkg: GrantPackageCandidate) => {
|
|
const packageName = pkg.package_name || '套餐名称不可用'
|
|
return `${packageName} - 成本价 ${formatPackageOptionPrice(pkg.cost_price)} - 建议售价 ${formatPackageOptionPrice(pkg.suggested_retail_price)}`
|
|
}
|
|
|
|
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 updateForm = (values: Partial<SeriesGrantPackageForm>) => {
|
|
emit('update:form', { ...props.form, ...values })
|
|
}
|
|
|
|
const updatePackageCost = (packageId: number, costPrice: number | undefined) => {
|
|
updateForm({
|
|
packages: props.form.packages.map((pkg) =>
|
|
pkg.package_id === packageId ? { ...pkg, cost_price_yuan: costPrice ?? 0 } : pkg
|
|
)
|
|
})
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
if (!formRef.value) return
|
|
try {
|
|
const valid = await formRef.value.validate()
|
|
if (valid) emit('submit')
|
|
} catch {
|
|
// 表单校验失败时由表单项展示提示
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(visible) => {
|
|
if (visible) nextTick(() => formRef.value?.clearValidate())
|
|
}
|
|
)
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.form-tip {
|
|
margin-top: 4px;
|
|
font-size: 12px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
|
|
:global(.package-select-dropdown .el-select-dropdown__item) {
|
|
height: auto;
|
|
min-height: 34px;
|
|
padding-top: 6px;
|
|
padding-bottom: 6px;
|
|
line-height: 1.5;
|
|
white-space: normal;
|
|
}
|
|
|
|
.package-config-list {
|
|
width: 100%;
|
|
}
|
|
|
|
.package-config-item {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
.package-config-name {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.package-config-cost {
|
|
display: flex;
|
|
flex-shrink: 0;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.package-config-field-label {
|
|
font-size: 13px;
|
|
color: var(--el-text-color-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.dialog-footer {
|
|
text-align: right;
|
|
}
|
|
</style>
|