优化管理后台
This commit is contained in:
@@ -41,6 +41,10 @@ export interface GoodsCreateRequest {
|
||||
valid_days?: number
|
||||
description?: string
|
||||
status?: number
|
||||
/** 虚拟支付道具 ID(需与微信后台一致) */
|
||||
product_id?: string
|
||||
/** 虚拟支付道具发布状态 0=未发布 2=已发布 */
|
||||
product_status?: number
|
||||
}
|
||||
|
||||
export interface GoodsUpdateRequest {
|
||||
@@ -54,6 +58,10 @@ export interface GoodsUpdateRequest {
|
||||
outfit_share_quota?: number
|
||||
valid_days?: number
|
||||
description?: string
|
||||
/** 虚拟支付道具 ID(传空串表示解除登记) */
|
||||
product_id?: string
|
||||
/** 虚拟支付道具发布状态 0=未发布 2=已发布 */
|
||||
product_status?: number
|
||||
}
|
||||
|
||||
export interface GoodsStatusRequest {
|
||||
|
||||
@@ -85,6 +85,12 @@ export const PRODUCT_STATUS = enumMap([
|
||||
{ value: 3, label: '发布失败', tagType: 'danger' },
|
||||
])
|
||||
|
||||
/** 虚拟支付道具发布状态(表单可选项:仅未发布/已发布两态) */
|
||||
export const PRODUCT_STATUS_EDITABLE = enumMap([
|
||||
{ value: 0, label: '未发布', tagType: 'info' },
|
||||
{ value: 2, label: '已发布', tagType: 'success' },
|
||||
])
|
||||
|
||||
/* ---------------- 订单 / 退款 ---------------- */
|
||||
export const ORDER_STATUS = enumMap([
|
||||
{ value: 1, label: '待支付', tagType: 'warning' },
|
||||
|
||||
@@ -6,7 +6,15 @@ import SearchFormCard from '@/components/SearchFormCard.vue'
|
||||
import PaginationBar from '@/components/PaginationBar.vue'
|
||||
import { createGoods, deleteGoods, getGoodsList, updateGoods, updateGoodsStatus } from '@/api/goods'
|
||||
import type { GoodsItem } from '@/types/goods'
|
||||
import { GOODS_STATUS, GOODS_TYPE, PRODUCT_STATUS, enumLabel, enumOptions, enumTag } from '@/utils/constants'
|
||||
import {
|
||||
GOODS_STATUS,
|
||||
GOODS_TYPE,
|
||||
PRODUCT_STATUS,
|
||||
PRODUCT_STATUS_EDITABLE,
|
||||
enumLabel,
|
||||
enumOptions,
|
||||
enumTag,
|
||||
} from '@/utils/constants'
|
||||
import { formatDate, formatMoney, formatText, yuanToCents } from '@/utils/format'
|
||||
|
||||
/* ---------------- 列表与筛选 ---------------- */
|
||||
@@ -60,6 +68,8 @@ const dialogVisible = ref(false)
|
||||
const dialogLoading = ref(false)
|
||||
const editId = ref<number | null>(null)
|
||||
const formRef = ref<FormInstance>()
|
||||
/** 编辑前的售价快照(分),用于已发布道具的改价提醒 */
|
||||
const originalDiscountPrice = ref<number | null>(null)
|
||||
|
||||
interface GoodsForm {
|
||||
goods_code: string
|
||||
@@ -74,6 +84,10 @@ interface GoodsForm {
|
||||
outfit_share_quota: number
|
||||
valid_days: number
|
||||
description: string
|
||||
/** 虚拟支付道具 ID(需与微信后台一致) */
|
||||
product_id: string
|
||||
/** 虚拟支付道具发布状态 0=未发布 2=已发布 */
|
||||
product_status: number
|
||||
}
|
||||
|
||||
const form = reactive<GoodsForm>({
|
||||
@@ -89,6 +103,8 @@ const form = reactive<GoodsForm>({
|
||||
outfit_share_quota: 0,
|
||||
valid_days: 0,
|
||||
description: '',
|
||||
product_id: '',
|
||||
product_status: 0,
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
@@ -103,10 +119,29 @@ const rules: FormRules = {
|
||||
goods_type: [{ required: true, message: '请选择商品类型', trigger: 'change' }],
|
||||
price: [{ required: true, message: '请输入原价', trigger: 'blur' }],
|
||||
discount_price: [{ required: true, message: '请输入售价', trigger: 'blur' }],
|
||||
product_id: [
|
||||
{ max: 64, message: '道具 ID 不超过 64 字', trigger: 'blur' },
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.product_status === 2 && !String(value ?? '').trim()) {
|
||||
callback(new Error('发布状态为「已发布」时必须填写道具 ID'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
/** 发布状态变更时联动重校验道具 ID(校验结果由表单项自身展示) */
|
||||
function handleProductStatusChange() {
|
||||
formRef.value?.validateField('product_id', () => {})
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editId.value = null
|
||||
originalDiscountPrice.value = null
|
||||
Object.assign(form, {
|
||||
goods_code: '',
|
||||
goods_name: '',
|
||||
@@ -120,12 +155,15 @@ function openCreate() {
|
||||
outfit_share_quota: 0,
|
||||
valid_days: 0,
|
||||
description: '',
|
||||
product_id: '',
|
||||
product_status: 0,
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row: GoodsItem) {
|
||||
editId.value = row.id
|
||||
originalDiscountPrice.value = row.discount_price
|
||||
Object.assign(form, {
|
||||
goods_code: row.goods_code,
|
||||
goods_name: row.goods_name,
|
||||
@@ -139,6 +177,9 @@ function openEdit(row: GoodsItem) {
|
||||
outfit_share_quota: row.outfit_share_quota,
|
||||
valid_days: row.valid_days,
|
||||
description: row.description,
|
||||
product_id: row.product_id ?? '',
|
||||
// 历史脏值(1 发布中 / 3 发布失败,旧自动同步任务遗留)统一归一为「未发布」
|
||||
product_status: row.product_status === 2 ? 2 : 0,
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
@@ -146,10 +187,27 @@ function openEdit(row: GoodsItem) {
|
||||
async function submitForm() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
const price = yuanToCents(form.price)
|
||||
const discountPrice = yuanToCents(form.discount_price)
|
||||
|
||||
// 已发布道具改价提醒:微信按道具价格扣款,未同步会因发货金额校验失败导致用户付款拿不到权益
|
||||
if (
|
||||
editId.value !== null &&
|
||||
form.product_status === 2 &&
|
||||
originalDiscountPrice.value !== null &&
|
||||
discountPrice !== originalDiscountPrice.value
|
||||
) {
|
||||
const confirmed = await ElMessageBox.confirm(
|
||||
'该商品已登记为「已发布」的微信虚拟支付道具。修改售价后,请务必同步更新微信后台的道具价格,否则用户付款后将无法发货。确认继续保存吗?',
|
||||
'改价提醒',
|
||||
{ type: 'warning', confirmButtonText: '继续保存', cancelButtonText: '取消' },
|
||||
).catch(() => false)
|
||||
if (!confirmed) return
|
||||
}
|
||||
|
||||
dialogLoading.value = true
|
||||
try {
|
||||
const price = yuanToCents(form.price)
|
||||
const discountPrice = yuanToCents(form.discount_price)
|
||||
if (editId.value === null) {
|
||||
await createGoods({
|
||||
goods_code: form.goods_code,
|
||||
@@ -164,6 +222,8 @@ async function submitForm() {
|
||||
outfit_share_quota: form.outfit_share_quota,
|
||||
valid_days: form.valid_days,
|
||||
description: form.description || undefined,
|
||||
product_id: form.product_id.trim() || undefined,
|
||||
product_status: form.product_status,
|
||||
})
|
||||
ElMessage.success('商品创建成功')
|
||||
} else {
|
||||
@@ -178,6 +238,8 @@ async function submitForm() {
|
||||
outfit_share_quota: form.outfit_share_quota,
|
||||
valid_days: form.valid_days,
|
||||
description: form.description || undefined,
|
||||
product_id: form.product_id.trim(),
|
||||
product_status: form.product_status,
|
||||
})
|
||||
ElMessage.success('商品更新成功')
|
||||
}
|
||||
@@ -277,11 +339,14 @@ async function handleDelete(row: GoodsItem) {
|
||||
<el-table-column label="AI 额度" width="90" align="right">
|
||||
<template #default="{ row }: { row: GoodsItem }">{{ row.ai_recommend_quota }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="虚拟支付" width="110">
|
||||
<el-table-column label="虚拟支付" width="150">
|
||||
<template #default="{ row }: { row: GoodsItem }">
|
||||
<el-tag v-if="row.product_id" :type="enumTag(PRODUCT_STATUS, row.product_status)" size="small">
|
||||
{{ enumLabel(PRODUCT_STATUS, row.product_status) }}
|
||||
</el-tag>
|
||||
<template v-if="row.product_id">
|
||||
<el-tag :type="enumTag(PRODUCT_STATUS, row.product_status)" size="small">
|
||||
{{ enumLabel(PRODUCT_STATUS, row.product_status) }}
|
||||
</el-tag>
|
||||
<div class="product-id-text" :title="row.product_id">{{ row.product_id }}</div>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -389,6 +454,28 @@ async function handleDelete(row: GoodsItem) {
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-divider content-position="left">微信虚拟支付道具</el-divider>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="道具 ID" prop="product_id">
|
||||
<el-input v-model="form.product_id" maxlength="64" placeholder="微信后台的道具 ID,如 monthly_vip" />
|
||||
<div class="form-tip">需与微信后台保持一致:道具 ID、道具名称、售价</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发布状态" prop="product_status">
|
||||
<el-select v-model="form.product_status" style="width: 100%" @change="handleProductStatusChange">
|
||||
<el-option
|
||||
v-for="opt in enumOptions(PRODUCT_STATUS_EDITABLE)"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="form-tip">设为「已发布」后小程序才可下单;改价后必须同步微信侧</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -419,4 +506,13 @@ async function handleDelete(row: GoodsItem) {
|
||||
color: #9aa3c0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.product-id-text {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #9aa3c0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user