396 lines
12 KiB
Vue
396 lines
12 KiB
Vue
<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>
|