Files
lckt-admin/src/view/goods/article/index.vue
2025-09-01 22:36:18 +08:00

396 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div >
<div class="searchForm">
<searchForm
:key="searchConfigVersion"
:search="finalSearchConfig"
@searchData="searchData"
@resetData="resetData"
class="search-box searchForm"
/>
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="handleEdit()">新增</el-button>
</div>
<Content
@changePage="changePage"
:total="total"
v-model:tabloading="tableLoading"
v-model:currentPage="queryParams.page"
v-model:pageSize="queryParams.pageSize"
:data="tableData"
:config="ARTICLE_TABLE_CONFIG"
>
<template #status="{ row }">
<el-tag :type="tag(row.status).extend">{{ tag(row.status).label }}</el-tag>
</template>
<template #coverImg="{ row }">
<el-image
style="width: 100px; height: 100px"
:src="row.coverImg"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="[row.coverImg]"
show-progress
:initial-index="4"
fit="cover"
/>
</template>
<template #price="{ row }">
<span v-if="row.isFree === 1" class="text-green-600 font-bold">免费</span>
<span v-else>{{ formatPrice(row.price) }}</span>
</template>
<template #isFree="{ row }">
<el-switch
v-model="row.isFree"
:active-value="1"
:inactive-value="0"
:loading="row.loading"
:before-change="() => statusChangeBefore(row)"
active-text="免费"
inactive-text="付费"
/>
</template>
<template #UpdatedAt="{ row }">
{{ formatDate(row.UpdatedAt) }}
</template>
<template #operate="{ row }">
<el-button type="primary" link class="table-button" @click="handleDetail(row)">
<el-icon style="margin-right: 5px"><InfoFilled /></el-icon>查看
</el-button>
<el-button type="primary" link icon="edit" class="table-button" @click="handleEdit(row)">编辑</el-button>
<el-button type="primary" link icon="delete" @click="handleDelete(row)">删除</el-button>
</template>
</Content>
</div>
<!-- 在 template 的 Content 组件后添加 Dialog -->
<el-dialog
v-model="detailVisible"
title="文章详情"
width="60%"
destroy-on-close
>
<el-descriptions :column="2" border>
<el-descriptions-item label="封面图">
<el-image
style="width: 100px; height: 100px"
:src="detailData.coverImg"
:preview-src-list="[detailData.coverImg]"
fit="cover"
/>
</el-descriptions-item>
<el-descriptions-item label="文章标题">{{ detailData.title || EMPTY_STR }}</el-descriptions-item>
<el-descriptions-item label="是否免费">
<el-tag :type="detailData.isFree === 1 ? 'success' : 'warning'">
{{ detailData.isFree === 1 ? '免费' : '付费' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="价格" v-if="detailData.isFree === 0">
{{ formatPrice(detailData.price) }}
</el-descriptions-item>
<el-descriptions-item label="讲师">{{ detailData.teacherName || EMPTY_STR }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDate(detailData.UpdatedAt) }}</el-descriptions-item>
<el-descriptions-item label="文章内容" :span="2">
<div v-html="detailData.content"></div>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">
{{ detailData.desc || EMPTY_STR }}
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup>
import { ElMessage, ElMessageBox } from 'element-plus'
import searchForm from '@/components/searchForm/index.vue'
import Content from '@/components/content/index.vue'
import { del, list, edit } from '@/api/goods/index.js'
import { getCategoryList } from '@/api/category/category.js'
import { getTechers } from '@/api/goods/index.js'
import { ref, onMounted, watch, nextTick } from 'vue'
import {formatDate} from '@/utils/format'
import { ARTICLE_SEARCH_CONFIG, ARTICLE_TABLE_CONFIG } from '@/config'
import { InfoFilled } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const EMPTY_STR = '- -'
// 在 script setup 中添加响应式数据
const detailVisible = ref(false)
const detailData = ref({})
const categoryList = ref([])
const teacherList = ref([])
const searchConfigVersion = ref(0) // 用于强制更新搜索配置
const finalSearchConfig = ref(JSON.parse(JSON.stringify(ARTICLE_SEARCH_CONFIG))) // 最终的搜索配置
const tableLoading = ref(true), tableData = ref([])
const queryParams = ref({
page: 1,
pageSize: 10,
title: '',
categoryId: '',
teacherId: '',
}), total = ref(0)
// 监听分类和讲师数据变化
watch([categoryList, teacherList], () => {
searchConfigVersion.value++ // 强制更新搜索配置
}, { deep: true })
// 监听搜索配置变化强制更新searchForm组件
watch(finalSearchConfig, () => {
// 强制触发searchForm组件的重新渲染
searchConfigVersion.value++
}, { deep: true })
// 更新搜索配置的函数
const updateSearchConfig = () => {
const config = JSON.parse(JSON.stringify(ARTICLE_SEARCH_CONFIG))
// 更新分类选项
const categoryConfig = config.find(item => item.prop === 'categoryId')
if (categoryConfig) {
const filteredCategories = categoryList.value.filter(item => !item.url || item.url === '')
categoryConfig.children.list = [
{ label: '全部', value: '' },
...filteredCategories.map(item => ({
label: item.name,
value: item.ID
}))
]
console.log('直接更新分类选项:', {
total: categoryList.value.length,
filtered: filteredCategories.length,
options: categoryConfig.children.list
})
}
// 更新讲师选项
const teacherConfig = config.find(item => item.prop === 'teacherId')
if (teacherConfig) {
const teacherOptions = teacherList.value.map(item => {
const id = item.ID || item.id
const label = item.nick_name || item.username || item.name || `用户${id}`
console.log(`生成讲师选项: ID=${id}, Label=${label}`)
return { label, value: id }
})
teacherConfig.children.list = [
{ label: '全部', value: '' },
...teacherOptions
]
console.log('直接更新讲师选项:', {
total: teacherList.value.length,
options: teacherConfig.children.list
})
}
// 强制更新搜索配置
finalSearchConfig.value = JSON.parse(JSON.stringify(config))
}
// 格式化价格显示(分转元)
const formatPrice = (price) => {
if (!price || price === 0) return '0.00元'
return (price / 100).toFixed(2) + '元'
}
// 状态标签
const tag = (status) => {
const map = {
1: { extend: 'success', label: '已发布' },
0: { extend: 'info', label: '草稿' }
}
return map[status] || { extend: '', label: EMPTY_STR }
}
const searchData = (data) => {
if (data.times) {
data.startTime = data.times[0]
data.endTime = data.times[1]
}
queryParams.value.page = 1
queryParams.value = { ...queryParams.value, ...data }
delete queryParams.value.times
getList()
}
const resetData = () => {
queryParams.value = {
page: 1,
pageSize: 10,
title: '',
categoryId: '',
teacherId: '',
isFree: '',
status: ''
}
getList()
}
onMounted(async () => {
console.log('页面开始加载...')
// 获取分类和讲师数据
await getCategoryData()
await getTeacherData()
// 等待DOM更新和搜索配置计算
await nextTick()
// 确保搜索配置已更新
updateSearchConfig()
// 再次等待确保搜索配置完全更新
setTimeout(() => {
console.log('第一次延迟后再次更新搜索配置...')
updateSearchConfig()
// 再次延迟确保搜索配置完全更新
setTimeout(() => {
console.log('第二次延迟后开始获取列表...')
getList()
}, 100)
}, 100)
})
// 获取分类数据
async function getCategoryData() {
try {
const res = await getCategoryList()
if (res.code === 0) {
categoryList.value = res.data.list || []
console.log('获取到的分类数据:', categoryList.value.length, '条')
console.log('分类数据详情:', categoryList.value.map(item => ({ id: item.ID, name: item.name, url: item.url })))
// 立即更新搜索配置
updateSearchConfig()
// 强制触发响应式更新
await nextTick()
searchConfigVersion.value++
console.log('分类数据更新完成,版本号:', searchConfigVersion.value)
}
} catch (error) {
console.error('获取分类数据失败:', error)
}
}
// 获取讲师数据
async function getTeacherData() {
try {
const res = await getTechers({ pageSize: 1000 }) // 获取所有讲师
if (res.code === 0) {
teacherList.value = res.data.list || []
console.log('获取到的讲师数据:', teacherList.value.length, '条')
console.log('讲师数据详情:', teacherList.value.map(item => ({
id: item.ID || item.id,
nick_name: item.nick_name,
username: item.username,
name: item.name
})))
// 调试:检查每个讲师对象的完整结构
teacherList.value.forEach((teacher, index) => {
console.log(`讲师${index + 1}完整数据:`, teacher)
})
// 立即更新搜索配置
updateSearchConfig()
// 强制触发响应式更新
await nextTick()
searchConfigVersion.value++
console.log('讲师数据更新完成,版本号:', searchConfigVersion.value)
}
} catch (error) {
console.error('获取讲师数据失败:', error)
}
}
function handleDetail(row) {
detailData.value = { ...row }
detailVisible.value = true
}
async function getList() {
const res = await list(queryParams.value)
if(res.code === 0) {
tableData.value = res.data.list
// 为每行数据添加loading状态
for(let item of tableData.value) {
item.loading = false
}
total.value = res.data.total
}
}
function handleEdit(row) {
router.push({
name: 'edit',
query: {
id: row?row.ID:''
}
})
}
// 切换免费/付费状态
async function statusChangeBefore(row) {
row.loading = true
try {
const res = await edit({
ID: row.ID,
isFree: row.isFree === 1 ? 0 : 1
})
if(res.code === 0) {
ElMessage.success('状态更新成功')
row.isFree = row.isFree === 1 ? 0 : 1
} else {
ElMessage.error(res.msg || '状态更新失败')
// 恢复原状态
row.isFree = row.isFree === 1 ? 0 : 1
}
} catch {
ElMessage.error('状态更新失败')
// 恢复原状态
row.isFree = row.isFree === 1 ? 0 : 1
} finally {
row.loading = false
}
}
async function handleDelete(row) {
try {
await ElMessageBox.confirm('此操作将永久删除该文章,是否继续?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
const res = await del({
ids: [row.ID]
})
if (res.code === 0) {
ElMessage.success('删除成功')
getList() // 刷新列表
}
} catch (error) {
// 取消删除时不需要提示
if (error !== 'cancel') {
ElMessage.error(error.message || '删除失败')
}
}
}
function changePage(data) {
queryParams.value.pageNum = data
getList()
}
</script>
<style lang="scss" scoped>
</style>