🎨 优化vip,文章列表页,兑换码管理页面
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
:border="!!config?.border"
|
||||
:data="computedData"
|
||||
ref="table"
|
||||
row-key="nanoId"
|
||||
:row-key="config?.rowKey || 'ID'"
|
||||
v-bind="computedStyle"
|
||||
:show-overflow-tooltip="config.tooltip"
|
||||
:header-row-style="headerRowStyle"
|
||||
@@ -54,6 +54,7 @@
|
||||
v-show="isShow"
|
||||
v-model:current-page="current"
|
||||
v-model:page-size="size"
|
||||
:page-sizes="[10, 30, 50, 100,500]"
|
||||
:background="props.background"
|
||||
:layout="props.layout"
|
||||
:total="props.total"
|
||||
|
||||
@@ -187,6 +187,19 @@
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
||||
<el-pagination
|
||||
v-model:current-page="itemQueryParams.page"
|
||||
v-model:page-size="itemQueryParams.pageSize"
|
||||
:page-sizes="[50, 100, 200, 500]"
|
||||
:total="itemTotal"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleItemSizeChange"
|
||||
@current-change="handleItemCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleSelectItemClose">取消</el-button>
|
||||
@@ -288,8 +301,13 @@ const itemOptionsLoading = ref(false)
|
||||
const selectedItem = ref(null)
|
||||
const selectedItemName = ref('')
|
||||
const itemSearchKeyword = ref('')
|
||||
const itemQueryParams = ref({
|
||||
page: 1,
|
||||
pageSize: 100
|
||||
})
|
||||
const itemTotal = ref(0)
|
||||
|
||||
// 过滤后的项目选项
|
||||
// 过滤后的项目选项(前端搜索过滤)
|
||||
const filteredItemOptions = computed(() => {
|
||||
if (!itemSearchKeyword.value) {
|
||||
return itemOptions.value
|
||||
@@ -401,7 +419,10 @@ const loadItemOptions = async () => {
|
||||
itemOptionsLoading.value = true
|
||||
try {
|
||||
let res
|
||||
const params = { page: 1, pageSize: 1000 }
|
||||
const params = {
|
||||
page: itemQueryParams.value.page,
|
||||
pageSize: itemQueryParams.value.pageSize
|
||||
}
|
||||
|
||||
switch (mkForm.value.type) {
|
||||
case 1: // VIP
|
||||
@@ -415,19 +436,23 @@ const loadItemOptions = async () => {
|
||||
break
|
||||
default:
|
||||
itemOptions.value = []
|
||||
itemTotal.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
if (res.code === 0) {
|
||||
itemOptions.value = res.data.list || []
|
||||
itemTotal.value = res.data.total || 0
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取数据失败')
|
||||
itemOptions.value = []
|
||||
itemTotal.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载项目选项失败:', error)
|
||||
ElMessage.error('加载数据失败')
|
||||
itemOptions.value = []
|
||||
itemTotal.value = 0
|
||||
} finally {
|
||||
itemOptionsLoading.value = false
|
||||
}
|
||||
@@ -457,11 +482,33 @@ const handleSelectItemClose = () => {
|
||||
selectItemDialogVisible.value = false
|
||||
selectedItem.value = null
|
||||
itemSearchKeyword.value = ''
|
||||
// 重置分页参数
|
||||
itemQueryParams.value = {
|
||||
page: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
}
|
||||
|
||||
// 处理项目搜索
|
||||
const handleItemSearch = () => {
|
||||
// 搜索逻辑已在 computed 中处理
|
||||
// 搜索逻辑已在 computed 中处理(前端过滤)
|
||||
// 如果有搜索关键词,重置到第一页以便查看所有结果
|
||||
if (itemSearchKeyword.value) {
|
||||
itemQueryParams.value.page = 1
|
||||
}
|
||||
}
|
||||
|
||||
// 处理项目分页大小变化
|
||||
const handleItemSizeChange = (size) => {
|
||||
itemQueryParams.value.pageSize = size
|
||||
itemQueryParams.value.page = 1
|
||||
loadItemOptions()
|
||||
}
|
||||
|
||||
// 处理项目分页变化
|
||||
const handleItemCurrentChange = (page) => {
|
||||
itemQueryParams.value.page = page
|
||||
loadItemOptions()
|
||||
}
|
||||
|
||||
// 获取项目显示名称
|
||||
@@ -478,9 +525,9 @@ const loadItemNameCache = async () => {
|
||||
try {
|
||||
// 获取所有类型的数据并缓存名称
|
||||
const [vipRes, teacherVipRes, goodsRes] = await Promise.all([
|
||||
vipList({ page: 1, pageSize: 1000 }),
|
||||
teacherVipList({ page: 1, pageSize: 1000 }),
|
||||
goodsList({ page: 1, pageSize: 1000 })
|
||||
vipList({ page: 1, pageSize: 100 }),
|
||||
teacherVipList({ page: 1, pageSize: 100 }),
|
||||
goodsList({ page: 1, pageSize: 100 })
|
||||
])
|
||||
|
||||
// 缓存VIP名称
|
||||
@@ -557,7 +604,7 @@ async function getMkList() {
|
||||
// 获取兑换码库选项
|
||||
async function getMkOptions() {
|
||||
try {
|
||||
const res = await mkList({ page: 1, pageSize: 1000 })
|
||||
const res = await mkList({ page: 1, pageSize: 100 })
|
||||
if (res.code === 0) {
|
||||
mkOptions.value = res.data.list || []
|
||||
}
|
||||
@@ -597,7 +644,7 @@ const handleEditMk = async (row) => {
|
||||
const handleManageCdk = (row) => {
|
||||
// 跳转到兑换码管理页面,传递兑换码库ID和名称
|
||||
router.push({
|
||||
path: 'cdkManage',
|
||||
name: 'CdkManage',
|
||||
query: {
|
||||
id: row.ID,
|
||||
name: encodeURIComponent(row.codeName)
|
||||
@@ -650,7 +697,7 @@ const handleExportClose = () => {
|
||||
const loadDomainOptions = async () => {
|
||||
domainOptionsLoading.value = true
|
||||
try {
|
||||
const res = await domainList({ page: 1, pageSize: 1000, status: 1 })
|
||||
const res = await domainList({ page: 1, pageSize: 100, status: 1 })
|
||||
if (res.code === 0) {
|
||||
domainOptions.value = res.data.list || []
|
||||
} else {
|
||||
|
||||
@@ -12,15 +12,19 @@
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="handleEdit()">新增</el-button>
|
||||
<el-button type="danger" icon="delete" :disabled="!selectedIds.length" @click="handleBatchDelete">批量删除</el-button>
|
||||
</div>
|
||||
<Content
|
||||
ref="contentRef"
|
||||
@changePage="changePage"
|
||||
@selection="handleSelection"
|
||||
:total="total"
|
||||
v-model:tabloading="tableLoading"
|
||||
v-model:currentPage="queryParams.page"
|
||||
v-model:pageSize="queryParams.pageSize"
|
||||
:data="tableData"
|
||||
:config="ARTICLE_TABLE_CONFIG"
|
||||
:maxHeight="tableMaxHeight"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
|
||||
@@ -65,6 +69,19 @@
|
||||
</template>
|
||||
</Content>
|
||||
</div>
|
||||
<!-- 悬浮回到顶部 -->
|
||||
<el-backtop :right="40" :bottom="80" />
|
||||
<!-- 悬浮滚动到底部按钮 -->
|
||||
<div
|
||||
class="scroll-to-bottom"
|
||||
@click="scrollToBottom"
|
||||
title="滚动到底部"
|
||||
>
|
||||
|
||||
<svg viewBox="0 0 1024 1024" width="18" height="18">
|
||||
<path d="M512 704L160 352h704z" fill="#409EFF"/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- 在 template 的 Content 组件后添加 Dialog -->
|
||||
<el-dialog
|
||||
v-model="detailVisible"
|
||||
@@ -121,6 +138,7 @@
|
||||
|
||||
const router = useRouter()
|
||||
const EMPTY_STR = '- -'
|
||||
const contentRef = ref(null)
|
||||
// 在 script setup 中添加响应式数据
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref({})
|
||||
@@ -130,6 +148,8 @@
|
||||
const finalSearchConfig = ref(JSON.parse(JSON.stringify(ARTICLE_SEARCH_CONFIG))) // 最终的搜索配置
|
||||
|
||||
const tableLoading = ref(true), tableData = ref([])
|
||||
const selectedIds = ref([])
|
||||
const tableMaxHeight = ref('70vh')
|
||||
const queryParams = ref({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
@@ -352,6 +372,10 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
// 表格勾选变化
|
||||
function handleSelection({ newSelection }) {
|
||||
selectedIds.value = (newSelection || []).map(item => item.ID)
|
||||
}
|
||||
|
||||
// 切换免费/付费状态
|
||||
async function statusChangeBefore(row) {
|
||||
@@ -377,6 +401,18 @@
|
||||
row.loading = false
|
||||
}
|
||||
}
|
||||
// 滚动到底部(针对表格滚动容器)
|
||||
function scrollToBottom() {
|
||||
try {
|
||||
const bodyWrapper = contentRef?.value?.table?.$refs?.bodyWrapper
|
||||
if (bodyWrapper) {
|
||||
bodyWrapper.scrollTo({ top: bodyWrapper.scrollHeight, behavior: 'smooth' })
|
||||
return
|
||||
}
|
||||
} catch (e) { console.warn('scrollToBottom error', e) }
|
||||
// 兜底:滚动整个页面
|
||||
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
try {
|
||||
@@ -400,10 +436,47 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
// 批量删除
|
||||
async function handleBatchDelete() {
|
||||
if (!selectedIds.value.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条记录吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
|
||||
const res = await del({ ids: selectedIds.value })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
selectedIds.value = []
|
||||
getList()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.message || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
function changePage(data) {
|
||||
queryParams.value.page = data
|
||||
getList()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.scroll-to-bottom{
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
bottom: 30px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
background: #ecf5ff;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 9;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<template #default="scope">{{ formatLevel(scope.row.level) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column sortable align="left" label="价格" prop="price" width="120">
|
||||
<template #default="scope">¥{{ Number(scope.row.price).toFixed(2) }}</template>
|
||||
<template #default="scope">¥{{ (Number(scope.row.price) / 100).toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column sortable align="left" label="有效期(天)" prop="expiration" width="120" />
|
||||
<el-table-column align="left" label="描述" prop="des" min-width="200" show-overflow-tooltip />
|
||||
@@ -259,7 +259,9 @@ const updateRow = async (row) => {
|
||||
const res = await apiDetail(row.ID)
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = { ...res.data }
|
||||
const detail = res.data || {}
|
||||
const priceInYuan = detail.price != null ? Number((Number(detail.price) / 100).toFixed(2)) : undefined
|
||||
formData.value = { ...detail, price: priceInYuan }
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
@@ -319,15 +321,20 @@ const enterDialog = async () => {
|
||||
elFormRef.value?.validate(async (valid) => {
|
||||
if (!valid) return (btnLoading.value = false)
|
||||
let res
|
||||
const payload = {
|
||||
...formData.value,
|
||||
// 价格按元输入,提交时转为分,确保为整数
|
||||
price: Math.round(Number(formData.value.price || 0) * 100),
|
||||
}
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await apiAdd(formData.value)
|
||||
res = await apiAdd(payload)
|
||||
break
|
||||
case 'update':
|
||||
res = await apiEdit(formData.value)
|
||||
res = await apiEdit(payload)
|
||||
break
|
||||
default:
|
||||
res = await apiAdd(formData.value)
|
||||
res = await apiAdd(payload)
|
||||
break
|
||||
}
|
||||
btnLoading.value = false
|
||||
|
||||
Reference in New Issue
Block a user