@@ -122,6 +122,12 @@
|
||||
<el-tag v-else-if="row.isProduct" type="success" size="small" style="margin-left: 8px;">商品</el-tag>
|
||||
|
||||
<span class="group-name">{{ row.name }}</span>
|
||||
<el-tag
|
||||
v-if="(row.isGroup && row.recommendWords) || (row.isProduct && row.data?.recommendWords)"
|
||||
type="danger"
|
||||
size="small"
|
||||
style="margin-left: 6px;"
|
||||
>{{ row.isGroup ? row.recommendWords : row.data?.recommendWords }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -207,6 +213,7 @@
|
||||
<div class="group-name-cell">
|
||||
<el-avatar v-if="row.cover" :size="32" :src="row.cover" />
|
||||
<span class="group-name">{{ row.name }}</span>
|
||||
<el-tag v-if="row.recommendWords" type="danger" size="small" style="margin-left: 6px;">{{ row.recommendWords }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -393,6 +400,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推荐词 -->
|
||||
<el-form-item prop="recommend_words" label="推荐词">
|
||||
<el-input v-model="groupForm.recommend_words" placeholder="推荐词(可选,如:热销、新品)" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-form-item prop="note" label="备注" class="note-form-item">
|
||||
<el-input
|
||||
@@ -761,6 +773,9 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
</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-switch
|
||||
v-model="productForm.send_notice"
|
||||
@@ -893,7 +908,8 @@ const groupForm = reactive({
|
||||
cover_id: undefined,
|
||||
cover_url: '',
|
||||
tag_id: undefined,
|
||||
index: 0
|
||||
index: 0,
|
||||
recommend_words: ''
|
||||
})
|
||||
|
||||
const groupRules = {
|
||||
@@ -949,7 +965,8 @@ const productForm = reactive({
|
||||
max_first_purchase_duration: 0,
|
||||
send_notice: false,
|
||||
renew_price: 0,
|
||||
renew_recommend_rebate: 0
|
||||
renew_recommend_rebate: 0,
|
||||
recommend_words: ''
|
||||
})
|
||||
|
||||
const productRules = {
|
||||
@@ -1496,7 +1513,8 @@ const handleAdd = (parentRow) => {
|
||||
cover_id: undefined,
|
||||
cover_url: '',
|
||||
tag_id: parentTagId || undefined,
|
||||
index: 0
|
||||
index: 0,
|
||||
recommend_words: ''
|
||||
})
|
||||
console.log('添加子级,父级信息:', parentRow.name, 'ID:', parentRow.id, 'Level:', parentRow.level, '标签:', parentRow.tag)
|
||||
} else {
|
||||
@@ -1511,7 +1529,8 @@ const handleAdd = (parentRow) => {
|
||||
cover_id: undefined,
|
||||
cover_url: '',
|
||||
tag_id: undefined,
|
||||
index: 0
|
||||
index: 0,
|
||||
recommend_words: ''
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1532,7 +1551,8 @@ const handleEdit = (row) => {
|
||||
cover_id: row.coverId || undefined,
|
||||
cover_url: row.cover || '',
|
||||
tag_id: row.tag?.id || row.tagId || undefined,
|
||||
index: row.index || 0
|
||||
index: row.index || 0,
|
||||
recommend_words: row.recommendWords || ''
|
||||
})
|
||||
|
||||
dialogVisible.value = true
|
||||
@@ -1602,7 +1622,8 @@ const handleAddProduct = () => {
|
||||
arg_type: 'all',
|
||||
require_real_name: false,
|
||||
max_per_user: 0,
|
||||
max_first_purchase_duration: 0
|
||||
max_first_purchase_duration: 0,
|
||||
recommend_words: ''
|
||||
})
|
||||
|
||||
selectedProductGroup.value = null
|
||||
@@ -1642,7 +1663,8 @@ const handleEditProduct = (product, parentGroupId) => {
|
||||
max_first_purchase_duration: product.maxFirstPurchaseDuration ?? product.max_first_purchase_duration ?? 0,
|
||||
send_notice: !!product.sendNotice,
|
||||
renew_price: (product.renewPrice ?? product.renew_price ?? 0) / 100,
|
||||
renew_recommend_rebate: product.renewRecommendRebate ?? product.renew_recommend_rebate ?? 0
|
||||
renew_recommend_rebate: product.renewRecommendRebate ?? product.renew_recommend_rebate ?? 0,
|
||||
recommend_words: product.recommendWords ?? product.recommend_words ?? ''
|
||||
})
|
||||
|
||||
productDialogVisible.value = true
|
||||
@@ -1674,7 +1696,8 @@ const submitProductForm = () => {
|
||||
max_first_purchase_duration: Number(productForm.max_first_purchase_duration) || 0,
|
||||
send_notice: productForm.send_notice === true,
|
||||
renew_price: Number(productForm.renew_price) || 0,
|
||||
renew_recommend_rebate: Number(productForm.renew_recommend_rebate) || 0
|
||||
renew_recommend_rebate: Number(productForm.renew_recommend_rebate) || 0,
|
||||
recommend_words: productForm.recommend_words || ''
|
||||
}
|
||||
|
||||
let res
|
||||
@@ -1813,7 +1836,8 @@ const submitForm = () => {
|
||||
name: groupForm.name.trim(),
|
||||
note: groupForm.note || '',
|
||||
disable: groupForm.disable,
|
||||
index: Number(groupForm.index) || 0
|
||||
index: Number(groupForm.index) || 0,
|
||||
recommend_words: groupForm.recommend_words || ''
|
||||
}
|
||||
|
||||
if (groupForm.parent_id) {
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
<div class="stat-value">{{ counts.expired }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card" :class="{ active: query.status === 'pending' }" @click="handleStatusCard('pending')">
|
||||
<div class="stat-icon stat-icon-pending"><el-icon><Timer /></el-icon></div>
|
||||
<div class="stat-body">
|
||||
<div class="stat-label">待开通</div>
|
||||
<div class="stat-value">{{ counts.pending }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card" :class="{ active: query.status === 'deleted' }" @click="handleStatusCard('deleted')">
|
||||
<div class="stat-icon stat-icon-deleted"><el-icon><Delete /></el-icon></div>
|
||||
<div class="stat-body">
|
||||
@@ -60,6 +67,7 @@
|
||||
<el-option label="全部" value="all" />
|
||||
<el-option label="正常" value="normal" />
|
||||
<el-option label="已到期" value="expired" />
|
||||
<el-option label="待开通" value="pending" />
|
||||
<el-option label="已删除" value="deleted" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -133,6 +141,7 @@
|
||||
<el-tag size="small" type="danger">已删除</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else-if="isExpired(row)" size="small" type="warning">已到期</el-tag>
|
||||
<el-tag v-else-if="row.status === 'pending'" size="small" type="info">待开通</el-tag>
|
||||
<el-tag v-else size="small" type="success">正常</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -513,7 +522,7 @@
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search, ArrowDown, Box, CircleCheck, Clock, Delete } from '@element-plus/icons-vue'
|
||||
import { Plus, Refresh, Search, ArrowDown, Box, CircleCheck, Clock, Delete, Timer } from '@element-plus/icons-vue'
|
||||
import { getUserGoodsList, getUserGoodsCount, createUserGoods, updateUserGoods, deleteUserGoods, getUserVmList, getExpireRemindList, sendExpireRemind } from '@/api/admin/userVm'
|
||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||
import { formatToApiTime } from '@/utils/tool'
|
||||
@@ -536,8 +545,8 @@ const showFilterUserSelector = ref(false)
|
||||
const showFilterProductSelector = ref(false)
|
||||
|
||||
// 用户商品数量统计
|
||||
const counts = reactive({ normal: 0, deleted: 0, expired: 0 })
|
||||
const countTotal = computed(() => (counts.normal || 0) + (counts.deleted || 0) + (counts.expired || 0))
|
||||
const counts = reactive({ normal: 0, deleted: 0, expired: 0, pending: 0 })
|
||||
const countTotal = computed(() => (counts.normal || 0) + (counts.deleted || 0) + (counts.expired || 0) + (counts.pending || 0))
|
||||
|
||||
const formatTime = (t) => t ? dayjs(t).format('YYYY-MM-DD HH:mm:ss') : '-'
|
||||
|
||||
@@ -616,6 +625,7 @@ const loadCount = async () => {
|
||||
counts.normal = d.normal ?? 0
|
||||
counts.deleted = d.deleted ?? 0
|
||||
counts.expired = d.expired ?? 0
|
||||
counts.pending = d.pending ?? 0
|
||||
}
|
||||
} catch { /* 统计失败不阻断列表 */ }
|
||||
}
|
||||
@@ -1054,6 +1064,7 @@ onMounted(() => { loadList(); loadCount() })
|
||||
.stat-icon-normal { background: #f0faf0; color: #52c41a; }
|
||||
.stat-icon-expired { background: #fff7e6; color: #fa8c16; }
|
||||
.stat-icon-deleted { background: #fff1f0; color: #f5222d; }
|
||||
.stat-icon-pending { background: #e6f7ff; color: #1890ff; }
|
||||
|
||||
.stat-body { display: flex; flex-direction: column; }
|
||||
.stat-label { font-size: 13px; color: #909399; margin-bottom: 2px; }
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<span class="price-symbol">¥</span>
|
||||
<span class="price-amount">{{ formatPlanPrice(row) }}</span>
|
||||
</div>
|
||||
<div v-if="isFixedPrice(row) && getRenewPrice(row) > 0" class="plan-renew-price">
|
||||
续费 ¥{{ formatRenewPrice(row) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-stat plan-stat-inventory" :class="getInventoryClass(row)">
|
||||
<div class="plan-stat-label">
|
||||
@@ -471,6 +474,14 @@ const formatPlanPrice = (row) => {
|
||||
return (Number(raw) / 100).toFixed(2)
|
||||
}
|
||||
|
||||
const getRenewPrice = (row) => {
|
||||
return Number(row.renewFixedPrice ?? row.renew_fixed_price ?? 0)
|
||||
}
|
||||
|
||||
const formatRenewPrice = (row) => {
|
||||
return (getRenewPrice(row) / 100).toFixed(2)
|
||||
}
|
||||
|
||||
const getInventoryNum = (row) => {
|
||||
return Number(row.inventory ?? 0) || 0
|
||||
}
|
||||
@@ -1018,6 +1029,14 @@ watch(() => props.visible, (val) => {
|
||||
color: #f56c6c;
|
||||
font-weight: 700;
|
||||
}
|
||||
.plan-renew-price {
|
||||
font-size: 11px;
|
||||
color: #e6a23c;
|
||||
font-weight: 500;
|
||||
margin-top: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.plan-stat-price.is-dynamic {
|
||||
background: linear-gradient(135deg, #f0f7ff 0%, #e6f2ff 100%);
|
||||
border-color: #c6e2ff;
|
||||
|
||||
Reference in New Issue
Block a user