feat: enhance AWS and product management
Build and Deploy Vue3 / build (push) Successful in 1m49s
Build and Deploy Vue3 / deploy (push) Successful in 3m19s

This commit is contained in:
shiran
2026-09-05 17:23:48 +08:00
parent d0171659bd
commit 543bbb0b69
10 changed files with 1085 additions and 335 deletions
+130 -100
View File
@@ -526,8 +526,9 @@
ref="productFormRef"
:model="productForm"
:rules="productRules"
label-position="top"
class="product-form"
label-position="right"
label-width="116px"
class="product-form goods-form"
>
<!-- 顶部封面 + 基本信息 -->
<div class="product-form-header">
@@ -608,40 +609,26 @@
<el-tabs v-model="productFormTab" class="product-form-tabs">
<el-tab-pane label="价格与销售" name="sales">
<div class="form-grid">
<el-form-item prop="price">
<template #label>
<span>商品价格<span class="unit-suffix"></span></span>
</template>
<el-input-number
v-model="productForm.price"
:min="0"
:precision="2"
:step="0.01"
placeholder="请输入价格"
controls-position="right"
style="width: 100%"
/>
<el-form-item label="商品价格" prop="price">
<div class="unit-input-row">
<el-input-number v-model="productForm.price" :min="0" :precision="2" :step="0.01" placeholder="请输入价格" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item label="单个商品数量" prop="pay_num">
<el-input-number
v-model="productForm.pay_num"
:min="1"
placeholder="请输入数量"
controls-position="right"
style="width: 100%"
/>
<div class="unit-input-row">
<el-input-number v-model="productForm.pay_num" :min="1" placeholder="请输入数量" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item prop="expire_time">
<template #label>
<span>有效期<span class="unit-suffix">0 为永久</span></span>
<span class="form-label-with-help">有效期<el-tooltip content="0 表示永久有效" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span>
</template>
<el-input-number
v-model="productForm.expire_time"
:min="0"
placeholder="请输入有效期"
controls-position="right"
style="width: 100%"
/>
<div class="unit-input-row">
<el-input-number v-model="productForm.expire_time" :min="0" placeholder="请输入有效期" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item label="允许续费" prop="can_renew">
<el-switch
@@ -652,17 +639,30 @@
</el-form-item>
<el-form-item prop="renew_price" v-if="productForm.can_renew">
<template #label>
<span>续费价格<span class="unit-suffix">0 沿用商品价格</span></span>
<span class="form-label-with-help">续费价格<el-tooltip content="0 表示沿用商品价格" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span>
</template>
<el-input-number
v-model="productForm.renew_price"
:min="0"
:precision="2"
:step="0.01"
placeholder="续费基础价格"
controls-position="right"
style="width: 100%"
/>
<div class="unit-input-row">
<el-input-number v-model="productForm.renew_price" :min="0" :precision="2" :step="0.01" placeholder="续费基础价格" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item prop="max_renew_duration" v-if="productForm.can_renew">
<template #label>
<span class="form-label-with-help">最长续费时长<el-tooltip content="限制用户单次续费可选择的最大月数,0 表示不限制" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span>
</template>
<div class="unit-input-row">
<el-input-number v-model="productForm.max_renew_duration" :min="0" :precision="0" :step="1" placeholder="0 表示不限" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item prop="renew_before_days" v-if="productForm.can_renew">
<template #label>
<span class="form-label-with-help">提前续费天数<el-tooltip content="仅在商品到期前指定天数内开放续费,0 表示随时允许" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span>
</template>
<div class="unit-input-row">
<el-input-number v-model="productForm.renew_before_days" :min="0" :precision="0" :step="1" placeholder="0 表示不限" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item label="购买类型" prop="arg_type">
<el-select
@@ -675,21 +675,21 @@
<el-option label="仅自定义参数" value="customize" />
</el-select>
</el-form-item>
<el-form-item label="需要实名" prop="require_real_name">
<el-form-item prop="require_real_name">
<template #label><span class="form-label-with-help">需要实名<el-tooltip content="启用后,用户购买、续费或升级此商品前须完成实名认证" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span></template>
<el-switch
v-model="productForm.require_real_name"
active-text="需要"
inactive-text="不需要"
/>
<div style="font-size:12px;color:#909399;margin-top:4px">启用后用户购买/续费/升级此商品前须完成实名认证</div>
</el-form-item>
<el-form-item label="远端商品" prop="remote_goods">
<el-form-item prop="remote_goods">
<template #label><span class="form-label-with-help">远端商品<el-tooltip content="AWS 商品需同时将标签设为 aws,保存后到 AWS 商品绑定页配置目录规则" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span></template>
<el-switch
v-model="productForm.remote_goods"
active-text="远端计价/履约"
inactive-text="普通商品"
/>
<div style="font-size:12px;color:#909399;margin-top:4px">AWS 商品请同时将标签设为 aws保存后到 AWS 商品绑定页配置目录规则</div>
</el-form-item>
</div>
</el-tab-pane>
@@ -704,43 +704,33 @@
/>
</el-form-item>
<el-form-item label="库存数量" prop="inventory">
<el-input-number
v-model="productForm.inventory"
:min="0"
:disabled="!productForm.inventory_control"
placeholder="请输入库存"
controls-position="right"
style="width: 100%"
/>
<div class="unit-input-row">
<el-input-number v-model="productForm.inventory" :min="0" :disabled="!productForm.inventory_control" placeholder="请输入库存" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item label="售罄状态" prop="sold_out">
<el-form-item prop="sold_out">
<template #label><span class="form-label-with-help">售罄状态<el-tooltip content="开启后用户端将无法选购该商品" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span></template>
<el-switch
v-model="productForm.sold_out"
active-text="售罄"
inactive-text="正常"
active-color="#f56c6c"
/>
<div style="font-size:12px;color:#909399;margin-top:4px">开启后用户端将无法选购该商品</div>
</el-form-item>
<el-form-item label="购买限制" prop="max_per_user">
<el-input-number
v-model="productForm.max_per_user"
:min="0"
placeholder="0 表示不限"
controls-position="right"
style="width: 100%"
/>
<div style="font-size:12px;color:#909399;margin-top:4px">限制单用户最大购买数量0 表示不限制</div>
<el-form-item prop="max_per_user">
<template #label><span class="form-label-with-help">购买限制<el-tooltip content="限制单用户最大购买数量,0 表示不限制" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span></template>
<div class="unit-input-row">
<el-input-number v-model="productForm.max_per_user" :min="0" placeholder="0 表示不限" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
<el-form-item label="首购最大时长" prop="max_first_purchase_duration">
<el-input-number
v-model="productForm.max_first_purchase_duration"
:min="0"
placeholder="0 表示不限"
controls-position="right"
style="width: 100%"
/>
<div style="font-size:12px;color:#909399;margin-top:4px">限制用户首次购买的最大时长单位0 表示不限制</div>
<el-form-item prop="max_first_purchase_duration">
<template #label><span class="form-label-with-help">首购最大时长<el-tooltip content="限制用户首次购买的最大月数,0 表示不限制" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span></template>
<div class="unit-input-row">
<el-input-number v-model="productForm.max_first_purchase_duration" :min="0" placeholder="0 表示不限" controls-position="right" />
<span class="unit-text"></span>
</div>
</el-form-item>
</div>
</el-tab-pane>
@@ -754,44 +744,31 @@
inactive-text="禁用"
/>
</el-form-item>
<el-form-item prop="recommend_rebate">
<template #label>
<span>推荐返还<span class="unit-suffix">%</span></span>
</template>
<el-input-number
v-model="productForm.recommend_rebate"
:min="0"
:max="100"
:disabled="!productForm.recommend"
placeholder="返还百分比"
controls-position="right"
style="width: 100%"
/>
<el-form-item label="推荐返还" prop="recommend_rebate">
<div class="unit-input-row">
<el-input-number v-model="productForm.recommend_rebate" :min="0" :max="100" :disabled="!productForm.recommend" placeholder="返还百分比" controls-position="right" />
<span class="unit-text">%</span>
</div>
</el-form-item>
<el-form-item prop="renew_recommend_rebate">
<template #label>
<span>续费推介返还<span class="unit-suffix">%0 沿用推荐返还</span></span>
<span class="form-label-with-help">续费推介返还<el-tooltip content="0 表示沿用推荐返还比例" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span>
</template>
<el-input-number
v-model="productForm.renew_recommend_rebate"
:min="0"
:max="100"
:disabled="!productForm.recommend"
placeholder="续费推介返还百分比"
controls-position="right"
style="width: 100%"
/>
<div class="unit-input-row">
<el-input-number v-model="productForm.renew_recommend_rebate" :min="0" :max="100" :disabled="!productForm.recommend" placeholder="续费推介返还百分比" controls-position="right" />
<span class="unit-text">%</span>
</div>
</el-form-item>
<el-form-item label="推荐词" prop="recommend_words">
<el-input v-model="productForm.recommend_words" placeholder="推荐词(可选,如:热销、新品)" clearable style="width: 100%" />
</el-form-item>
<el-form-item label="购买通知" prop="send_notice">
<el-form-item prop="send_notice">
<template #label><span class="form-label-with-help">购买通知<el-tooltip content="用户购买后是否向管理员发送通知" placement="top"><el-icon><QuestionFilled /></el-icon></el-tooltip></span></template>
<el-switch
v-model="productForm.send_notice"
active-text="发送"
inactive-text="不发送"
/>
<div style="font-size:12px;color:#909399;margin:4px">用户购买后是否发送通知给管理员</div>
</el-form-item>
</div>
</el-tab-pane>
@@ -856,7 +833,7 @@
import { ref, reactive, computed, onMounted, onActivated, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search, Folder, ArrowRight, Loading, Grid, List, Document, Picture, Delete, CollectionTag } from '@element-plus/icons-vue'
import { Plus, Refresh, Search, Folder, ArrowRight, Loading, Grid, List, Document, Picture, Delete, CollectionTag, QuestionFilled } from '@element-plus/icons-vue'
import {
getProductGroupList,
createProductGroup,
@@ -973,6 +950,8 @@ const productForm = reactive({
sold_out: false,
max_per_user: 0,
max_first_purchase_duration: 0,
max_renew_duration: 0,
renew_before_days: 0,
send_notice: false,
renew_price: 0,
renew_recommend_rebate: 0,
@@ -1634,6 +1613,8 @@ const handleAddProduct = () => {
remote_goods: false,
max_per_user: 0,
max_first_purchase_duration: 0,
max_renew_duration: 0,
renew_before_days: 0,
recommend_words: ''
})
@@ -1673,6 +1654,8 @@ const handleEditProduct = (product, parentGroupId) => {
sold_out: !!product.soldOut,
max_per_user: product.maxPerUser ?? product.max_per_user ?? 0,
max_first_purchase_duration: product.maxFirstPurchaseDuration ?? product.max_first_purchase_duration ?? 0,
max_renew_duration: product.maxRenewDuration ?? product.max_renew_duration ?? 0,
renew_before_days: product.renewBeforeDays ?? product.renew_before_days ?? 0,
send_notice: !!product.sendNotice,
renew_price: (product.renewPrice ?? product.renew_price ?? 0) / 100,
renew_recommend_rebate: product.renewRecommendRebate ?? product.renew_recommend_rebate ?? 0,
@@ -1707,6 +1690,8 @@ const submitProductForm = () => {
sold_out: productForm.sold_out === true,
max_per_user: Number(productForm.max_per_user) || 0,
max_first_purchase_duration: Number(productForm.max_first_purchase_duration) || 0,
max_renew_duration: Number(productForm.max_renew_duration) || 0,
renew_before_days: Number(productForm.renew_before_days) || 0,
send_notice: productForm.send_notice === true,
renew_price: Number(productForm.renew_price) || 0,
renew_recommend_rebate: Number(productForm.renew_recommend_rebate) || 0,
@@ -2140,8 +2125,9 @@ watch(() => route.query.good_id, (newId) => {
margin-top: 4px;
}
.unit-input-row { display: flex; align-items: center; gap: 6px; width: 100%; }
.unit-text { font-size: 13px; color: #606266; flex-shrink: 0; white-space: nowrap; }
.unit-input-row { display: flex; align-items: center; gap: 8px; width: 100%; min-width: 0; }
.unit-input-row :deep(.el-input-number) { width: 100%; min-width: 0; flex: 1; }
.unit-text { width: 22px; color: #606266; font-size: 13px; flex-shrink: 0; white-space: nowrap; }
.recommend-user-selector {
display: flex;
@@ -2168,6 +2154,41 @@ watch(() => route.query.good_id, (newId) => {
--section-gap: 20px;
}
.goods-form :deep(.el-form-item__label) {
display: inline-flex;
height: 32px;
align-items: center;
justify-content: flex-end;
padding-right: 12px;
color: #303133;
line-height: 32px;
}
.goods-form :deep(.el-form-item__content) {
min-width: 0;
line-height: 32px;
}
.form-label-with-help {
display: inline-flex;
min-width: 0;
align-items: center;
justify-content: flex-end;
gap: 4px;
white-space: nowrap;
}
.form-label-with-help .el-icon {
color: #909399;
font-size: 14px;
cursor: help;
transition: color .2s ease;
}
.form-label-with-help .el-icon:hover {
color: #409eff;
}
.product-form-header {
display: flex;
gap: 24px;
@@ -2269,6 +2290,10 @@ watch(() => route.query.good_id, (newId) => {
margin-bottom: 12px;
}
.goods-form .basic-fields :deep(.el-form-item) {
align-items: flex-start;
}
.basic-fields :deep(.el-form-item:last-child) {
margin-bottom: 0;
}
@@ -2393,6 +2418,11 @@ watch(() => route.query.good_id, (newId) => {
margin-bottom: 12px;
}
.goods-form .form-grid :deep(.el-form-item) {
min-width: 0;
align-items: flex-start;
}
.form-grid .arg-type-item {
grid-column: span 2;
}