基本完成内容管理模块所有子项前后端增删改查交互
This commit is contained in:
615
package-lock.json
generated
615
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -94,7 +94,7 @@
|
||||
|
||||
<el-form-item label="封面图">
|
||||
<el-upload class="cover-uploader" :show-file-list="false" :on-success="handleCoverSuccess"
|
||||
:before-upload="beforeCoverUpload" action="http://localhost:3000/api/upload/">
|
||||
:before-upload="beforeCoverUpload" action="http://desktop-2t92coa.local:3000/api/upload/">
|
||||
<img v-if="articleSettings.coverImage" :src="articleSettings.coverImage" class="cover-image" />
|
||||
<el-icon v-else class="cover-uploader-icon">
|
||||
<Plus />
|
||||
@@ -314,7 +314,7 @@ onMounted(() => {
|
||||
|
||||
// 加载文章数据
|
||||
const loadArticleData = () => {
|
||||
fetch('http://localhost:3000/api/articles/' + route.params.id, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles/' + route.params.id, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
const { content, title, ...rest } = data.data;
|
||||
@@ -398,7 +398,7 @@ function saveDraftCore() {
|
||||
ElMessage.error('请输入文章内容');
|
||||
return;
|
||||
}
|
||||
fetch('http://localhost:3000/api/articles/' + (isEdit.value ? route.params.id : 0), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles/' + (isEdit.value ? route.params.id : 0), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...articleForm, ...articleSettings }),
|
||||
headers: {
|
||||
@@ -429,7 +429,7 @@ const handlePublish = () => {
|
||||
ElMessage.error('请输入文章内容')
|
||||
return
|
||||
}
|
||||
fetch('http://localhost:3000/api/articles/' + (isEdit.value ? route.params.id : 0), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles/' + (isEdit.value ? route.params.id : 0), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...articleForm, ...articleSettings }),
|
||||
headers: {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-input v-model="searchForm.title" placeholder="请输入文章标题" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-select v-model="searchForm.category" placeholder="请选择分类" clearable style="width: 150px">
|
||||
<el-select v-model="searchForm.category" placeholder="请选择分类" clearable style="width: 160px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option v-for="item in classificationOptions" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
@@ -15,7 +15,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
placeholder="请选择状态" clearable style="width: 120px">
|
||||
placeholder="请选择状态" clearable style="width: 220px">
|
||||
<el-option label="草稿" value="isDraft=1" />
|
||||
<el-option label="发布" value="isPublished=1" />
|
||||
<el-option label="下架" value="isPublished=0" />
|
||||
@@ -143,7 +143,7 @@
|
||||
<template #default="{ row }">
|
||||
<div class="status-tags">
|
||||
<el-tag v-for="status in getArticleStatuses(row)" :key="status.key" :type="status.type" size="small"
|
||||
class="status-tag">
|
||||
style="margin-right: 5px;" class="status-tag">
|
||||
{{ status.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
<el-table :data="recycleTableData" stripe border style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="title" label="文章名称" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="title" label="文章标题" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="updateTime" label="删除时间" width="200">
|
||||
<template #default="{ row }">{{ row.updateTime ? new Date(row.updateTime).toLocaleString() : ''
|
||||
}}</template>
|
||||
@@ -286,7 +286,7 @@ onMounted(() => {
|
||||
// 加载数据
|
||||
const loadData = (searchParams = '') => {
|
||||
loading.value = true
|
||||
fetch('http://localhost:3000/api/articles?isDeleted=0&page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + searchParams, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles?isDeleted=0&page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + searchParams, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
tableData.value = data.data
|
||||
@@ -364,7 +364,7 @@ const handleDelete = async (row) => {
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
fetch(`http://localhost:3000/api/articles/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/articles/${row.id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -425,7 +425,7 @@ const handleBatchOffline = () => {
|
||||
|
||||
// 上架请求
|
||||
const handlePublishArticles = (ids) => {
|
||||
fetch('http://localhost:3000/api/articles/publish', {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles/publish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
@@ -445,7 +445,7 @@ const handlePublishArticles = (ids) => {
|
||||
|
||||
// 下架请求
|
||||
const handleOfflineArticles = (ids) => {
|
||||
fetch('http://localhost:3000/api/articles/unpublish', {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles/unpublish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
@@ -488,7 +488,7 @@ const handleExport = (format) => {
|
||||
showClose: false
|
||||
})
|
||||
|
||||
fetch(`http://localhost:3000/api/articles/export?format=${format}&${buildSearchParams()}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/articles/export?format=${format}&${buildSearchParams()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -586,7 +586,7 @@ const openRecycleBin = () => {
|
||||
// 加载回收站数据
|
||||
const loadRecycleData = () => {
|
||||
recycleTableData.value = []
|
||||
fetch('http://localhost:3000/api/articles?isDeleted=1&title=' + recycleSearch.value, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/articles?isDeleted=1&title=' + recycleSearch.value, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
recycleTableData.value = data.data
|
||||
@@ -600,7 +600,7 @@ const handleRecover = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/articles/recover/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/articles/recover/${row.id}`, {
|
||||
method: 'POST'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -625,7 +625,7 @@ const handleForceDelete = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/articles/force/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/articles/force/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -686,7 +686,7 @@ const handleCurrentChange = (val) => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 获取文章状态数组
|
||||
// 获取状态数组
|
||||
const getArticleStatuses = (row) => {
|
||||
const statuses = []
|
||||
// 基础状态
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="upload-section">
|
||||
<el-upload class="file-uploader" :show-file-list="false" :on-success="handleFileSuccess"
|
||||
:before-upload="beforeFileUpload" :on-progress="handleUploadProgress"
|
||||
action="http://localhost:3000/api/upload" :accept="acceptedFileTypes">
|
||||
action="http://desktop-2t92coa.local:3000/api/upload" :accept="acceptedFileTypes">
|
||||
<div v-if="!downloadForm.attachmentUrl" class="upload-area">
|
||||
<el-icon class="upload-icon">
|
||||
<Upload />
|
||||
@@ -212,7 +212,7 @@
|
||||
<el-table-column prop="title" label="文章名称" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="updateTime" label="删除时间" width="200">
|
||||
<template #default="{ row }">{{ row.updateTime ? new Date(row.updateTime).toLocaleString() : ''
|
||||
}}</template>
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
@@ -293,12 +293,19 @@ onMounted(() => {
|
||||
|
||||
// 加载文件数据
|
||||
const loadFileData = () => {
|
||||
fetch('http://localhost:3000/api/files/' + route.params.id, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files/' + route.params.id, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
const { title, attachmentUrl, fileName, fileSize, uploadTime, description, ...rest } = data.data;
|
||||
Object.assign(downloadForm, { title, attachmentUrl, fileName, fileType: fileName.split('.').pop().toLowerCase(), fileSize, uploadTime: new Date(uploadTime).toLocaleString(), description })
|
||||
Object.assign(fileSettings, rest)
|
||||
const booleanFields = ['isPublished', 'isTop', 'isRecommended', 'isHot', 'isSlideshow', 'isDraft'];
|
||||
const convertedRest = { ...rest };
|
||||
booleanFields.forEach(key => {
|
||||
if (key in convertedRest) {
|
||||
convertedRest[key] = convertedRest[key] === 1 ? true : false;
|
||||
}
|
||||
});
|
||||
Object.assign(fileSettings, convertedRest)
|
||||
Object.assign(fileSettingsCopy, fileSettings)
|
||||
}).catch(error => {
|
||||
console.error('加载文件数据失败:', error)
|
||||
@@ -354,7 +361,7 @@ const handleSaveDraft = () => {
|
||||
downloadForm.isDraft = true
|
||||
downloadForm.publishTime = null
|
||||
downloadForm.isPublished = false
|
||||
fetch('http://localhost:3000/api/files/' + (isEdit.value ? route.params.id : 0), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files/' + (isEdit.value ? route.params.id : 0), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(downloadForm),
|
||||
headers: {
|
||||
@@ -385,7 +392,7 @@ const handlePublish = () => {
|
||||
downloadForm.isPublished = true
|
||||
downloadForm.isDraft = false
|
||||
downloadForm.publishTime = new Date().toLocaleString()
|
||||
fetch('http://localhost:3000/api/files/' + (isEdit.value ? route.params.id : 0), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files/' + (isEdit.value ? route.params.id : 0), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(downloadForm),
|
||||
headers: {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-input v-model="searchForm.title" placeholder="请输入文件标题" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型">
|
||||
<el-select v-model="searchForm.fileType" placeholder="请选择文件类型" clearable style="width: 150px">
|
||||
<el-select v-model="searchForm.fileType" placeholder="请选择文件类型" clearable style="width: 160px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="PDF文档" value="pdf" />
|
||||
<el-option label="Word文档" value="doc" />
|
||||
@@ -19,14 +19,18 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已发布" value="published" />
|
||||
<el-option label="已下架" value="offline" />
|
||||
<el-select v-model="searchForm.status" multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
placeholder="请选择状态" clearable style="width: 220px">
|
||||
<el-option label="草稿" value="isDraft=1" />
|
||||
<el-option label="发布" value="isPublished=1" />
|
||||
<el-option label="下架" value="isPublished=0" />
|
||||
<el-option label="置顶" value="isTop=1" />
|
||||
<el-option label="推荐" value="isRecommended=1" />
|
||||
<el-option label="热点" value="isHot=1" />
|
||||
<el-option label="幻灯" value="isSlideshow=1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间">
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker v-model="searchForm.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期"
|
||||
end-placeholder="结束日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 240px" />
|
||||
</el-form-item>
|
||||
@@ -140,7 +144,7 @@
|
||||
<template #default="{ row }">
|
||||
<div class="status-tags">
|
||||
<el-tag v-for="status in getArticleStatuses(row)" :key="status.key" :type="status.type" size="small"
|
||||
class="status-tag">
|
||||
style="margin-right: 5px;" class="status-tag">
|
||||
{{ status.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@@ -287,7 +291,7 @@ const router = useRouter()
|
||||
const searchForm = reactive({
|
||||
title: '',
|
||||
fileType: '',
|
||||
status: '',
|
||||
status: [],
|
||||
dateRange: []
|
||||
})
|
||||
|
||||
@@ -318,7 +322,7 @@ onMounted(() => {
|
||||
// 加载数据
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
fetch('http://localhost:3000/api/files?page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + buildSearchParams(), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files?page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + buildSearchParams(), {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
tableData.value = data.data
|
||||
@@ -340,8 +344,10 @@ const buildSearchParams = () => {
|
||||
if (searchForm.fileType) {
|
||||
params.append('fileType', searchForm.fileType)
|
||||
}
|
||||
if (searchForm.status) {
|
||||
params.append('status', searchForm.status)
|
||||
if (searchForm.status.length > 0) {
|
||||
searchForm.status.forEach(status => {
|
||||
params.append(status.split('=')[0], status.split('=')[1])
|
||||
})
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
params.append('startDate', searchForm.dateRange[0])
|
||||
@@ -359,7 +365,7 @@ const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
title: '',
|
||||
fileType: '',
|
||||
status: '',
|
||||
status: [],
|
||||
dateRange: []
|
||||
})
|
||||
loadData()
|
||||
@@ -389,7 +395,7 @@ const handleDelete = async (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/files/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/files/${row.id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -450,7 +456,7 @@ const handleBatchOffline = () => {
|
||||
|
||||
// 上架请求
|
||||
const handlePublishFiles = (ids) => {
|
||||
fetch('http://localhost:3000/api/files/publish', {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files/publish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
@@ -470,7 +476,7 @@ const handlePublishFiles = (ids) => {
|
||||
|
||||
// 下架请求
|
||||
const handleOfflineFiles = (ids) => {
|
||||
fetch('http://localhost:3000/api/files/unpublish', {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files/unpublish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
@@ -587,7 +593,7 @@ const getFileTypeColor = (fileName) => {
|
||||
return colorMap[fileType] || '#909399'
|
||||
}
|
||||
|
||||
// 获取文章状态数组
|
||||
// 获取状态数组
|
||||
const getArticleStatuses = (row) => {
|
||||
const statuses = []
|
||||
// 基础状态
|
||||
@@ -627,7 +633,7 @@ const openRecycleBin = () => {
|
||||
// 加载回收站数据
|
||||
const loadRecycleData = () => {
|
||||
recycleTableData.value = []
|
||||
fetch('http://localhost:3000/api/files?isDeleted=1&title=' + recycleSearch.value, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/files?isDeleted=1&title=' + recycleSearch.value, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
recycleTableData.value = data.data
|
||||
@@ -641,7 +647,7 @@ const handleRecover = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/files/recover/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/files/recover/${row.id}`, {
|
||||
method: 'POST'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -666,7 +672,7 @@ const handleForceDelete = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/files/force/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/files/force/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -696,7 +702,7 @@ const handleRecoverAll = () => {
|
||||
|
||||
// 一键删除全部
|
||||
const handleForceDeleteAll = () => {
|
||||
ElMessageBox.confirm('确定要永久删除回收站中的全部文件吗?此操作不可恢复。', '永久删除全部', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
ElMessageBox.confirm('确定要永久删除回收站中的全部文件吗?此操作不可恢复。', '永久删除全部', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量永久删除
|
||||
ElMessage.success('一键删除(占位)')
|
||||
loadRecycleData()
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
<el-input v-model="searchForm.name" placeholder="请输入链接名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="链接类型">
|
||||
<el-select v-model="searchForm.type" placeholder="请选择类型" clearable style="width: 150px">
|
||||
<el-select v-model="searchForm.type" placeholder="请选择类型" clearable style="width: 160px">
|
||||
<el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否发布">
|
||||
<el-select v-model="searchForm.published" placeholder="请选择" clearable style="width: 120px">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.isPublished" placeholder="请选择状态" clearable style="width: 220px">
|
||||
<el-option label="发布" :value="1" />
|
||||
<el-option label="下架" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -41,17 +41,35 @@
|
||||
<el-button type="warning" :disabled="!hasSelection" @click="handleBatchUnpublish"><el-icon>
|
||||
<Download />
|
||||
</el-icon> 批量下架</el-button>
|
||||
<el-button type="danger" :disabled="!hasSelection" @click="handleBatchDelete"><el-icon>
|
||||
<Delete />
|
||||
</el-icon> 批量删除</el-button>
|
||||
</div>
|
||||
<div class="action-right">
|
||||
<el-button @click="handleImport"><el-icon>
|
||||
<Upload />
|
||||
</el-icon> 导入</el-button>
|
||||
<el-button @click="handleExport"><el-icon>
|
||||
<Download />
|
||||
</el-icon> 导出</el-button>
|
||||
<el-dropdown @command="handleExport" trigger="click">
|
||||
<el-button>
|
||||
<el-icon>
|
||||
<Download />
|
||||
</el-icon>
|
||||
导出
|
||||
<el-icon class="el-icon--right">
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="json">导出为JSON</el-dropdown-item>
|
||||
<el-dropdown-item command="xlsx">导出为Excel</el-dropdown-item>
|
||||
<el-dropdown-item command="csv">导出为CSV</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button @click="openRecycleBin" type="danger">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
回收站
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -72,9 +90,9 @@
|
||||
<el-image v-if="row.image" :src="row.image" style="width:80px;height:20px" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="published" label="是否发布" width="100">
|
||||
<el-table-column prop="isPublished" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.published ? 'success' : 'info'">{{ row.published ? '已发布' : '未发布' }}</el-tag>
|
||||
<el-tag :type="row.isPublished ? 'success' : 'info'">{{ row.isPublished ? '已发布' : '已下架' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort" label="排序" width="80" />
|
||||
@@ -83,7 +101,7 @@
|
||||
<el-button type="primary" size="small" @click="handleEdit(row)"><el-icon>
|
||||
<Edit />
|
||||
</el-icon> 编辑</el-button>
|
||||
<el-button v-if="row.published" type="info" size="small" @click="handleUnpublish(row)"><el-icon>
|
||||
<el-button v-if="row.isPublished" type="info" size="small" @click="handleUnpublish(row)"><el-icon>
|
||||
<Download />
|
||||
</el-icon> 下架</el-button>
|
||||
<el-button v-else type="warning" size="small" @click="handlePublish(row)"><el-icon>
|
||||
@@ -131,7 +149,7 @@
|
||||
<el-form-item label="排序" prop="sort"><el-input-number v-model="form.sort" :min="0" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否发布" prop="published"><el-switch v-model="form.published" /></el-form-item>
|
||||
<el-form-item label="是否发布" prop="isPublished"><el-switch v-model="form.isPublished" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@@ -142,16 +160,50 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 回收站弹窗 -->
|
||||
<el-dialog v-model="recycleDialogVisible" title="回收站" width="60%">
|
||||
<div>
|
||||
<el-row :gutter="12" align="middle" style="margin-bottom:12px">
|
||||
<el-col :span="12" style="display: flex; align-items: center;">
|
||||
<span style="font-size: 16px; margin-right: 16px;">名称:</span>
|
||||
<el-input v-model="recycleSearch" placeholder="按链接名称搜索" style="width:200px" clearable />
|
||||
<el-button type="primary" @click="loadRecycleData" style="margin-left: 8px;">搜索</el-button>
|
||||
<el-button @click="recycleSearch = ''; loadRecycleData()" style="margin-left: 4px;">重置</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align: right;">
|
||||
<el-button type="success" :disabled="recycleTableData.length === 0"
|
||||
@click="handleRecoverAll">一键恢复</el-button>
|
||||
<el-button type="danger" :disabled="recycleTableData.length === 0" @click="handleForceDeleteAll"
|
||||
style="margin-left: 8px;">一键删除</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="recycleTableData" stripe border style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="链接名称" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="updatedTime" label="删除时间" width="200">
|
||||
<template #default="{ row }">{{ row.updateTime ? new Date(row.updateTime).toLocaleString() : ''
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleRecover(row)">恢复</el-button>
|
||||
<el-button type="danger" size="small" @click="handleForceDelete(row)">永久删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, Plus, Delete, Edit, Upload, Download } from '@element-plus/icons-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ name: '', type: '', published: '' })
|
||||
const searchForm = reactive({ name: '', type: '', isPublished: '' })
|
||||
|
||||
// 类型选项
|
||||
const typeOptions = ref([
|
||||
@@ -174,7 +226,7 @@ const dialogType = ref('add')
|
||||
const formRef = ref(null)
|
||||
|
||||
// 表单默认数据
|
||||
const form = reactive({ id: null, name: '', type: '', url: '', image: '', published: false, sort: 0 })
|
||||
const form = reactive({ id: null, name: '', type: '', url: '', image: '', isPublished: false, sort: 0 })
|
||||
|
||||
// 验证规则
|
||||
const rules = {
|
||||
@@ -190,35 +242,27 @@ const getTypeLabel = (type) => {
|
||||
return found ? found.label : '-'
|
||||
}
|
||||
|
||||
// 加载数据(静态模拟数据+过滤+分页)
|
||||
// 加载数据
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
const totalMock = 38
|
||||
const all = Array.from({ length: totalMock }).map((_, i) => {
|
||||
const id = i + 1
|
||||
const typeIdx = i % typeOptions.value.length
|
||||
return {
|
||||
id,
|
||||
name: `示例链接${id}`,
|
||||
type: typeOptions.value[typeIdx].value,
|
||||
url: `https://example.com/${id}`,
|
||||
image: id % 3 === 0 ? `https://via.placeholder.com/40x40?text=${id}` : '',
|
||||
published: i % 2 === 0,
|
||||
sort: id
|
||||
}
|
||||
})
|
||||
let filtered = all.filter(row => {
|
||||
if (searchForm.name && !row.name.includes(searchForm.name)) return false
|
||||
if (searchForm.type && row.type !== searchForm.type) return false
|
||||
if (searchForm.published !== '' && Number(row.published) !== Number(searchForm.published)) return false
|
||||
return true
|
||||
})
|
||||
pagination.total = filtered.length
|
||||
const start = (pagination.currentPage - 1) * pagination.pageSize
|
||||
tableData.value = filtered.slice(start, start + pagination.pageSize)
|
||||
fetch('http://desktop-2t92coa.local:3000/api/links?page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + buildSearchParams(), {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
tableData.value = data.data
|
||||
pagination.total = data.total
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}).catch(error => {
|
||||
ElMessage.error('加载失败' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
const buildSearchParams = () => {
|
||||
const params = new URLSearchParams()
|
||||
params.append('isDeleted', 0)
|
||||
if (searchForm.unitName) { params.append('unitName', searchForm.unitName) }
|
||||
if (searchForm.contact) { params.append('contact', searchForm.contact) }
|
||||
if (searchForm.isPublished) { params.append('isPublished', searchForm.isPublished) }
|
||||
return params.toString()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
@@ -226,57 +270,264 @@ onMounted(() => loadData())
|
||||
// 搜索
|
||||
const handleSearch = () => loadData()
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { name: '', type: '', published: '' })
|
||||
Object.assign(searchForm, { name: '', type: '', isPublished: '' })
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
dialogType.value = 'add'
|
||||
Object.assign(form, { id: null, name: '', type: '', url: '', image: '', published: false, sort: 0 })
|
||||
Object.assign(form, { id: null, name: '', type: '', url: '', image: '', isPublished: false, sort: 0 })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row) => {
|
||||
dialogType.value = 'edit'
|
||||
Object.assign(form, row)
|
||||
const booleanFields = ['isPublished', 'isTop', 'isRecommend', 'isHot', 'isSlideshow', 'isDraft'];
|
||||
const convertedRest = { ...row };
|
||||
booleanFields.forEach(key => {
|
||||
if (key in convertedRest) {
|
||||
convertedRest[key] = convertedRest[key] === 1 ? true : false;
|
||||
}
|
||||
});
|
||||
Object.assign(form, convertedRest)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm(`确定要删除 ${row.name} 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessage.success('删除成功'); loadData() })
|
||||
ElMessageBox.confirm(`确定要删除链接 ${row.name} 吗?`, '确认删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/links/${row.id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('删除成功')
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('删除失败' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = () => {
|
||||
if (!hasSelection.value) { ElMessage.warning('请选择记录'); return }
|
||||
ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 条记录吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessage.success('删除成功'); loadData() })
|
||||
// 上架
|
||||
const handlePublish = (row) => {
|
||||
ElMessageBox.confirm(`确定要上架链接 ${row.name} 吗?`, '确认上架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handlePublishLinks([row.id])
|
||||
})
|
||||
}
|
||||
|
||||
// 上架/下架
|
||||
const handlePublish = (row) => { ElMessage.info('上架功能占位'); }
|
||||
const handleUnpublish = (row) => { ElMessage.info('下架功能占位'); }
|
||||
const handleBatchPublish = () => { ElMessage.info('批量上架功能占位'); }
|
||||
const handleBatchUnpublish = () => { ElMessage.info('批量下架功能占位'); }
|
||||
// 下架
|
||||
const handleUnpublish = (row) => {
|
||||
ElMessageBox.confirm(`确定要下架链接 ${row.name} 吗?`, '确认下架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handleOfflineLinks([row.id])
|
||||
})
|
||||
}
|
||||
|
||||
// 批量上架
|
||||
const handleBatchPublish = () => {
|
||||
ElMessageBox.confirm(`确定要上架 ${selectedRows.value.length} 个链接吗?`, '确认上架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handlePublishLinks(selectedRows.value.map(row => row.id))
|
||||
})
|
||||
}
|
||||
|
||||
// 批量下架
|
||||
const handleBatchUnpublish = () => {
|
||||
ElMessageBox.confirm(`确定要下架 ${selectedRows.value.length} 个链接吗?`, '确认下架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handleOfflineLinks(selectedRows.value.map(row => row.id))
|
||||
})
|
||||
}
|
||||
|
||||
// 上架请求
|
||||
const handlePublishLinks = (ids) => {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/links/publish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('上架成功')
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('上架失败' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
// 下架请求
|
||||
const handleOfflineLinks = (ids) => {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/links/unpublish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('下架成功')
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('下架失败' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
// 导入/导出
|
||||
const handleImport = () => { ElMessage.info('导入功能占位') }
|
||||
const handleExport = () => { ElMessage.info('导出功能占位') }
|
||||
|
||||
// 提交表单(新增/修改占位)
|
||||
// 提交表单
|
||||
const handleSubmit = () => {
|
||||
if (!formRef.value) return
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||||
dialogVisible.value = false
|
||||
loadData()
|
||||
fetch('http://desktop-2t92coa.local:3000/api/links/' + (dialogType.value === 'add' ? 0 : form.id), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(form),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('添加成功')
|
||||
dialogVisible.value = false
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('添加失败' + error.message)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 回收站相关
|
||||
const recycleDialogVisible = ref(false)
|
||||
const recycleTableData = ref([])
|
||||
const recycleSearch = ref('')
|
||||
|
||||
const openRecycleBin = () => {
|
||||
recycleDialogVisible.value = true
|
||||
loadRecycleData()
|
||||
}
|
||||
|
||||
// 加载回收站数据
|
||||
const loadRecycleData = () => {
|
||||
recycleTableData.value = []
|
||||
fetch('http://desktop-2t92coa.local:3000/api/links?isDeleted=1&title=' + recycleSearch.value, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
recycleTableData.value = data.data
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复回收站链接
|
||||
const handleRecover = (row) => {
|
||||
ElMessageBox.confirm(`确定恢复链接 ${row.name} 吗?`, '确认恢复', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/links/recover/${row.id}`, {
|
||||
method: 'POST'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('恢复成功')
|
||||
loadRecycleData()
|
||||
if (recycleTableData.value.length === 0) {
|
||||
recycleDialogVisible.value = false
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('恢复失败' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 永久删除回收站链接
|
||||
const handleForceDelete = (row) => {
|
||||
ElMessageBox.confirm(`永久删除链接 ${row.name} 吗? 此操作不可恢复。`, '永久删除', {
|
||||
confirmButtonText: '删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/links/force/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('已永久删除')
|
||||
loadRecycleData()
|
||||
if (recycleTableData.value.length === 0) {
|
||||
recycleDialogVisible.value = false
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('删除失败' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 一键恢复全部
|
||||
const handleRecoverAll = () => {
|
||||
ElMessageBox.confirm('确定一键恢复回收站中的全部链接吗?', '确认恢复全部', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量恢复
|
||||
ElMessage.success('一键恢复(占位)')
|
||||
loadRecycleData()
|
||||
loadData()
|
||||
})
|
||||
}
|
||||
|
||||
// 一键删除全部
|
||||
const handleForceDeleteAll = () => {
|
||||
ElMessageBox.confirm('确定要永久删除回收站中的全部链接吗?此操作不可恢复。', '永久删除全部', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量永久删除
|
||||
ElMessage.success('一键删除(占位)')
|
||||
loadRecycleData()
|
||||
})
|
||||
}
|
||||
|
||||
// 监控:当回收站弹窗关闭时,加载数据
|
||||
watch(recycleDialogVisible, (newVal) => {
|
||||
if (!newVal) {
|
||||
loadData()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (selection) => { selectedRows.value = selection }
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<el-input v-model="searchForm.name" placeholder="请输入姓名" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="民族">
|
||||
<el-select v-model="searchForm.nation" placeholder="请选择民族" clearable style="width: 150px">
|
||||
<el-select v-model="searchForm.nation" placeholder="请选择民族" clearable style="width: 160px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option v-for="item in nationOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="自理情况">
|
||||
<el-select v-model="searchForm.selfCare" placeholder="请选择自理情况" clearable style="width: 150px">
|
||||
<el-select v-model="searchForm.selfCare" placeholder="请选择自理情况" clearable style="width: 160px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="完全自理" value="1" />
|
||||
<el-option label="半自理" value="2" />
|
||||
@@ -423,7 +423,7 @@ const handleDelete = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/services/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/services/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -453,7 +453,7 @@ const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
fetch("http://localhost:3000/api/services/" + (dialogType.value === 'add' ? 0 : form.id), {
|
||||
fetch("http://desktop-2t92coa.local:3000/api/services/" + (dialogType.value === 'add' ? 0 : form.id), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(form),
|
||||
headers: {
|
||||
@@ -477,7 +477,7 @@ const handleSubmit = async () => {
|
||||
// 加载数据
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
fetch("http://localhost:3000/api/services?page=" + pagination.currentPage + "&pageSize=" + pagination.pageSize + "&" + buildSearchParams(), {
|
||||
fetch("http://desktop-2t92coa.local:3000/api/services?page=" + pagination.currentPage + "&pageSize=" + pagination.pageSize + "&" + buildSearchParams(), {
|
||||
method: 'GET',
|
||||
}).then(response => response.json()).then(data => {
|
||||
tableData.value = data.data
|
||||
@@ -533,7 +533,7 @@ const openRecycleBin = () => {
|
||||
// 加载回收站数据
|
||||
const loadRecycleData = () => {
|
||||
recycleTableData.value = []
|
||||
fetch('http://localhost:3000/api/services?isDeleted=1&name=' + recycleSearch.value, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/services?isDeleted=1&name=' + recycleSearch.value, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
recycleTableData.value = data.data
|
||||
@@ -547,7 +547,7 @@ const handleRecover = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/services/recover/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/services/recover/${row.id}`, {
|
||||
method: 'POST'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -572,7 +572,7 @@ const handleForceDelete = (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/services/force/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/services/force/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<h4 class="section-title">视频文件</h4>
|
||||
<el-upload class="video-uploader" :show-file-list="false" :on-success="handleVideoSuccess"
|
||||
:before-upload="beforeVideoUpload" :on-progress="handleUploadProgress"
|
||||
action="http://localhost:3000/api/upload" accept="video/*">
|
||||
action="http://desktop-2t92coa.local:3000/api/upload" accept="video/*">
|
||||
<div v-if="!videoForm.videoUrl" class="upload-area">
|
||||
<el-icon class="upload-icon">
|
||||
<VideoPlay />
|
||||
@@ -125,8 +125,9 @@
|
||||
|
||||
<div v-else class="custom-thumbnail">
|
||||
<el-upload class="thumbnail-uploader" :show-file-list="false" :on-success="handleThumbnailSuccess"
|
||||
:before-upload="beforeThumbnailUpload" action="#" accept="image/*">
|
||||
<img v-if="videoForm.thumbnail" :src="videoForm.thumbnail" class="thumbnail-image" />
|
||||
:before-upload="beforeThumbnailUpload" action="http://desktop-2t92coa.local:3000/api/upload"
|
||||
accept="image/*">
|
||||
<img v-if="videoForm.coverImage" :src="videoForm.coverImage" class="thumbnail-image" />
|
||||
<div v-else class="thumbnail-placeholder" style="height: 150px; width:200px">
|
||||
<el-icon>
|
||||
<Picture />
|
||||
@@ -305,12 +306,19 @@ onMounted(() => {
|
||||
|
||||
// 加载视频数据
|
||||
const loadVideoData = () => {
|
||||
fetch('http://localhost:3000/api/videos/' + route.params.id, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos/' + route.params.id, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
const { title, content, classification, author, videoUrl, fileSize, coverImage, duration, ...rest } = data.data;
|
||||
Object.assign(videoForm, { title, content, classification, author, videoUrl, fileName: videoUrl.split('/').pop(), fileSize, coverImage })
|
||||
Object.assign(videoSettings, rest)
|
||||
const booleanFields = ['isPublished', 'isTop', 'isRecommended', 'isHot', 'isSlideshow', 'isDraft'];
|
||||
const convertedRest = { ...rest };
|
||||
booleanFields.forEach(key => {
|
||||
if (key in convertedRest) {
|
||||
convertedRest[key] = convertedRest[key] === 1 ? true : false;
|
||||
}
|
||||
});
|
||||
Object.assign(videoSettings, convertedRest)
|
||||
Object.assign(videoSettingsCopy, videoSettings)
|
||||
}).catch(error => {
|
||||
console.error('加载视频数据失败:', error)
|
||||
@@ -330,7 +338,7 @@ const initNewVideo = () => {
|
||||
fileSize: '',
|
||||
duration: '',
|
||||
resolution: '',
|
||||
thumbnail: ''
|
||||
coverImage: ''
|
||||
})
|
||||
|
||||
Object.assign(videoSettings, {
|
||||
@@ -370,7 +378,7 @@ const handleSaveDraft = () => {
|
||||
ElMessage.error('请上传视频')
|
||||
return
|
||||
}
|
||||
fetch('http://localhost:3000/api/videos/' + (isEdit.value ? route.params.id : 0), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos/' + (isEdit.value ? route.params.id : 0), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...videoForm, ...videoSettings }),
|
||||
headers: {
|
||||
@@ -402,7 +410,7 @@ const handlePublish = () => {
|
||||
ElMessage.error('请上传视频')
|
||||
return
|
||||
}
|
||||
fetch('http://localhost:3000/api/videos/' + (isEdit.value ? route.params.id : 0), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos/' + (isEdit.value ? route.params.id : 0), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...videoForm, ...videoSettings }),
|
||||
headers: {
|
||||
@@ -497,7 +505,7 @@ const removeVideo = () => {
|
||||
|
||||
// 缩略图上传成功
|
||||
const handleThumbnailSuccess = (response, file) => {
|
||||
videoForm.thumbnail = URL.createObjectURL(file.raw)
|
||||
videoForm.coverImage = response.url
|
||||
ElMessage.success('缩略图上传成功')
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-input v-model="searchForm.title" placeholder="请输入视频标题" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-select v-model="searchForm.classification" placeholder="请选择分类" clearable style="width: 150px">
|
||||
<el-select v-model="searchForm.classification" placeholder="请选择分类" clearable style="width: 160px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="教学视频" value="teaching" />
|
||||
<el-option label="宣传视频" value="promotion" />
|
||||
@@ -16,14 +16,18 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已发布" value="published" />
|
||||
<el-option label="已下架" value="offline" />
|
||||
<el-select v-model="searchForm.status" multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
placeholder="请选择状态" clearable style="width: 220px">
|
||||
<el-option label="草稿" value="isDraft=1" />
|
||||
<el-option label="发布" value="isPublished=1" />
|
||||
<el-option label="下架" value="isPublished=0" />
|
||||
<el-option label="置顶" value="isTop=1" />
|
||||
<el-option label="推荐" value="isRecommended=1" />
|
||||
<el-option label="热点" value="isHot=1" />
|
||||
<el-option label="幻灯" value="isSlideshow=1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间">
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker v-model="searchForm.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期"
|
||||
end-placeholder="结束日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 240px" />
|
||||
</el-form-item>
|
||||
@@ -126,7 +130,7 @@
|
||||
<template #default="{ row }">
|
||||
<div class="status-tags">
|
||||
<el-tag v-for="status in getArticleStatuses(row)" :key="status.key" :type="status.type" size="small"
|
||||
class="status-tag">
|
||||
style="margin-right: 5px;" class="status-tag">
|
||||
{{ status.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@@ -182,6 +186,7 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 视频播放弹窗 -->
|
||||
<el-dialog v-model="showVideoDialog" title="视频播放" width="80%">
|
||||
<h2 :style="{ color: videoDialogData.titleColor, textAlign: 'center' }">{{ videoDialogData.title }}</h2>
|
||||
<p style="font-size: 12px; color: #666; margin-bottom: 10px;">发布时间:{{ new
|
||||
@@ -190,12 +195,13 @@
|
||||
<video :src="videoDialogData.videoUrl" controls style="width: 200px" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 回收站弹窗 -->
|
||||
<el-dialog v-model="recycleDialogVisible" title="回收站" width="60%">
|
||||
<div>
|
||||
<el-row :gutter="12" align="middle" style="margin-bottom:12px">
|
||||
<el-col :span="12" style="display: flex; align-items: center;">
|
||||
<span style="font-size: 16px; margin-right: 16px;">标题:</span>
|
||||
<el-input v-model="recycleSearch" placeholder="按文章标题搜索" style="width:200px" clearable />
|
||||
<el-input v-model="recycleSearch" placeholder="按视频标题搜索" style="width:200px" clearable />
|
||||
<el-button type="primary" @click="loadRecycleData" style="margin-left: 8px;">搜索</el-button>
|
||||
<el-button @click="recycleSearch = ''; loadRecycleData()" style="margin-left: 4px;">重置</el-button>
|
||||
</el-col>
|
||||
@@ -209,7 +215,7 @@
|
||||
|
||||
<el-table :data="recycleTableData" stripe border style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="title" label="文章名称" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="title" label="视频标题" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="updatedTime" label="删除时间" width="200">
|
||||
<template #default="{ row }">{{ row.updatedTime ? new Date(row.updatedTime).toLocaleString() : ''
|
||||
}}</template>
|
||||
@@ -238,7 +244,7 @@ const videoDialogData = ref()
|
||||
const searchForm = reactive({
|
||||
title: '',
|
||||
classification: '',
|
||||
status: '',
|
||||
status: [],
|
||||
dateRange: []
|
||||
})
|
||||
|
||||
@@ -265,7 +271,7 @@ onMounted(() => {
|
||||
// 加载数据
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
fetch('http://localhost:3000/api/videos?page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + buildSearchParams(), {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos?page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + buildSearchParams(), {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
tableData.value = data.data
|
||||
@@ -287,8 +293,10 @@ const buildSearchParams = () => {
|
||||
if (searchForm.classification) {
|
||||
params.append('classification', searchForm.classification)
|
||||
}
|
||||
if (searchForm.status) {
|
||||
params.append('status', searchForm.status)
|
||||
if (searchForm.status.length > 0) {
|
||||
searchForm.status.forEach(status => {
|
||||
params.append(status.split('=')[0], status.split('=')[1])
|
||||
})
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
params.append('startDate', searchForm.dateRange[0])
|
||||
@@ -336,7 +344,7 @@ const handleDelete = async (row) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/videos/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/videos/${row.id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -413,7 +421,7 @@ const handleBatchOffline = () => {
|
||||
|
||||
// 上架请求
|
||||
const handlePublishVideos = (ids) => {
|
||||
fetch('http://localhost:3000/api/videos/publish', {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos/publish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
@@ -433,7 +441,7 @@ const handlePublishVideos = (ids) => {
|
||||
|
||||
// 下架请求
|
||||
const handleOfflineVideos = (ids) => {
|
||||
fetch('http://localhost:3000/api/videos/unpublish', {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos/unpublish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
@@ -473,21 +481,21 @@ const openRecycleBin = () => {
|
||||
// 加载回收站数据
|
||||
const loadRecycleData = () => {
|
||||
recycleTableData.value = []
|
||||
fetch('http://localhost:3000/api/videos?isDeleted=1&title=' + recycleSearch.value, {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/videos?isDeleted=1&title=' + recycleSearch.value, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
recycleTableData.value = data.data
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复回收站文章
|
||||
// 恢复回收站视频
|
||||
const handleRecover = (row) => {
|
||||
ElMessageBox.confirm(`确定恢复文章 "${row.title}" 吗?`, '确认恢复', {
|
||||
ElMessageBox.confirm(`确定恢复视频 "${row.title}" 吗?`, '确认恢复', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/videos/recover/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/videos/recover/${row.id}`, {
|
||||
method: 'POST'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -505,14 +513,14 @@ const handleRecover = (row) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 永久删除回收站文章
|
||||
// 永久删除回收站视频
|
||||
const handleForceDelete = (row) => {
|
||||
ElMessageBox.confirm(`永久删除文章 "${row.title}"? 此操作不可恢复。`, '永久删除', {
|
||||
ElMessageBox.confirm(`永久删除视频 "${row.title}"? 此操作不可恢复。`, '永久删除', {
|
||||
confirmButtonText: '删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://localhost:3000/api/videos/force/${row.id}`, {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/videos/force/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
@@ -532,7 +540,7 @@ const handleForceDelete = (row) => {
|
||||
|
||||
// 一键恢复全部
|
||||
const handleRecoverAll = () => {
|
||||
ElMessageBox.confirm('确定一键恢复回收站中的全部文章吗?', '确认恢复全部', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
ElMessageBox.confirm('确定一键恢复回收站中的全部视频吗?', '确认恢复全部', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量恢复
|
||||
ElMessage.success('一键恢复(占位)')
|
||||
loadRecycleData()
|
||||
@@ -542,7 +550,7 @@ const handleRecoverAll = () => {
|
||||
|
||||
// 一键删除全部
|
||||
const handleForceDeleteAll = () => {
|
||||
ElMessageBox.confirm('确定要永久删除回收站中的全部文章吗?此操作不可恢复。', '永久删除全部', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
ElMessageBox.confirm('确定要永久删除回收站中的全部视频吗?此操作不可恢复。', '永久删除全部', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量永久删除
|
||||
ElMessage.success('一键删除(占位)')
|
||||
loadRecycleData()
|
||||
@@ -595,7 +603,7 @@ const getCategoryType = (category) => {
|
||||
return typeMap[category] || ''
|
||||
}
|
||||
|
||||
// 获取文章状态数组
|
||||
// 获取状态数组
|
||||
const getArticleStatuses = (row) => {
|
||||
const statuses = []
|
||||
// 基础状态
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
<el-card class="search-card">
|
||||
<el-form :model="searchForm" :inline="true" class="search-form">
|
||||
<el-form-item label="单位名称">
|
||||
<el-input v-model="searchForm.unitName" placeholder="请输入单位名称" clearable style="width: 220px" />
|
||||
<el-input v-model="searchForm.unitName" placeholder="请输入单位名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="searchForm.contact" placeholder="联系人" clearable style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否发布">
|
||||
<el-select v-model="searchForm.published" placeholder="请选择" clearable style="width: 120px">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
placeholder="请选择状态" clearable style="width: 220px">
|
||||
<el-option label="草稿" value="isDraft=1" />
|
||||
<el-option label="发布" value="isPublished=1" />
|
||||
<el-option label="下架" value="isPublished=0" />
|
||||
<el-option label="置顶" value="isTop=1" />
|
||||
<el-option label="推荐" value="isRecommended=1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -39,17 +43,35 @@
|
||||
<el-button type="warning" :disabled="!hasSelection" @click="handleBatchUnpublish"><el-icon>
|
||||
<Download />
|
||||
</el-icon> 批量下架</el-button>
|
||||
<el-button type="danger" :disabled="!hasSelection" @click="handleBatchDelete"><el-icon>
|
||||
<Delete />
|
||||
</el-icon> 批量删除</el-button>
|
||||
</div>
|
||||
<div class="action-right">
|
||||
<el-button @click="handleImport"><el-icon>
|
||||
<Upload />
|
||||
</el-icon> 导入</el-button>
|
||||
<el-button @click="handleExport"><el-icon>
|
||||
<Download />
|
||||
</el-icon> 导出</el-button>
|
||||
<el-dropdown @command="handleExport" trigger="click">
|
||||
<el-button>
|
||||
<el-icon>
|
||||
<Download />
|
||||
</el-icon>
|
||||
导出
|
||||
<el-icon class="el-icon--right">
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="json">导出为JSON</el-dropdown-item>
|
||||
<el-dropdown-item command="xlsx">导出为Excel</el-dropdown-item>
|
||||
<el-dropdown-item command="csv">导出为CSV</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button @click="openRecycleBin" type="danger">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
回收站
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -65,18 +87,16 @@
|
||||
<el-table-column prop="phone" label="联系电话" width="140" />
|
||||
<el-table-column prop="url" label="URL链接" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="site" label="所属站点" width="120" />
|
||||
<el-table-column prop="published" label="是否发布" width="100">
|
||||
<el-table-column prop="status" label="状态" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.published ? 'success' : 'info'">{{ row.published ? '已发布' : '未发布' }}</el-tag>
|
||||
<div class="status-tags">
|
||||
<el-tag v-for="status in getArticleStatuses(row)" :key="status.key" :type="status.type" size="small"
|
||||
style="margin-right: 5px;" class="status-tag">
|
||||
{{ status.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isTop" label="置顶" width="80">
|
||||
<template #default="{ row }">{{ row.isTop ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isRecommend" label="推荐" width="90">
|
||||
<template #default="{ row }">{{ row.isRecommend ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort" label="排序" width="80" />
|
||||
<el-table-column label="操作" width="230" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleEdit(row)"><el-icon>
|
||||
@@ -129,7 +149,7 @@
|
||||
<el-form-item label="排序" prop="sort"><el-input-number v-model="form.sort" :min="0" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否发布" prop="published"><el-switch v-model="form.published" /></el-form-item>
|
||||
<el-form-item label="是否发布" prop="isPublished"><el-switch v-model="form.isPublished" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否置顶" prop="isTop"><el-switch v-model="form.isTop" /></el-form-item>
|
||||
@@ -140,14 +160,16 @@
|
||||
<el-divider content-position="left">上传相关</el-divider>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="LOGO图标" prop="logo">
|
||||
<el-upload action="#" list-type="picture" :show-file-list="false">
|
||||
<el-upload action="http://desktop-2t92coa.local:3000/api/upload" list-type="picture"
|
||||
:show-file-list="false">
|
||||
<el-button size="small">上传LOGO</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公众号二维码" prop="qrcode">
|
||||
<el-upload action="#" list-type="picture" :show-file-list="false">
|
||||
<el-upload action="http://desktop-2t92coa.local:3000/api/upload" list-type="picture"
|
||||
:show-file-list="false">
|
||||
<el-button size="small">上传二维码</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -161,16 +183,50 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 回收站弹窗 -->
|
||||
<el-dialog v-model="recycleDialogVisible" title="回收站" width="60%">
|
||||
<div>
|
||||
<el-row :gutter="12" align="middle" style="margin-bottom:12px">
|
||||
<el-col :span="12" style="display: flex; align-items: center;">
|
||||
<span style="font-size: 16px; margin-right: 16px;">名称:</span>
|
||||
<el-input v-model="recycleSearch" placeholder="按单位名称搜索" style="width:200px" clearable />
|
||||
<el-button type="primary" @click="loadRecycleData" style="margin-left: 8px;">搜索</el-button>
|
||||
<el-button @click="recycleSearch = ''; loadRecycleData()" style="margin-left: 4px;">重置</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align: right;">
|
||||
<el-button type="success" :disabled="recycleTableData.length === 0"
|
||||
@click="handleRecoverAll">一键恢复</el-button>
|
||||
<el-button type="danger" :disabled="recycleTableData.length === 0" @click="handleForceDeleteAll"
|
||||
style="margin-left: 8px;">一键删除</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="recycleTableData" stripe border style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="unitName" label="单位名称" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="updatedTime" label="删除时间" width="200">
|
||||
<template #default="{ row }">{{ row.updateTime ? new Date(row.updateTime).toLocaleString() : ''
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleRecover(row)">恢复</el-button>
|
||||
<el-button type="danger" size="small" @click="handleForceDelete(row)">永久删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, Plus, Delete, Edit, Upload, Download } from '@element-plus/icons-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ unitName: '', contact: '', published: '' })
|
||||
const searchForm = reactive({ unitName: '', contact: '', status: [] })
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
@@ -186,7 +242,7 @@ const dialogType = ref('add')
|
||||
const formRef = ref(null)
|
||||
|
||||
// 表单默认数据
|
||||
const form = reactive({ id: null, unitName: '', contact: '', phone: '', url: '', logo: '', qrcode: '', site: '', published: false, isTop: false, isRecommend: false, sort: 0 })
|
||||
const form = reactive({ id: null, unitName: '', contact: '', phone: '', url: '', logo: '', qrcode: '', site: '', isPublished: false, isTop: false, isRecommend: false, sort: 0 })
|
||||
|
||||
// 验证规则
|
||||
const rules = {
|
||||
@@ -202,17 +258,31 @@ const siteOptions = ref([
|
||||
|
||||
const hasSelection = computed(() => selectedRows.value.length > 0)
|
||||
|
||||
const getRowById = (id) => tableData.value.find(r => r.id === id) || null
|
||||
|
||||
// 加载数据(占位)
|
||||
// 加载数据
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
// 模拟数据
|
||||
tableData.value = Array.from({ length: 12 }).map((_, i) => ({ id: i + 1, unitName: `单位 ${i + 1}`, contact: `联系人 ${i + 1}`, phone: `1380000${1000 + i}`, url: 'https://example.com', site: i % 2 === 0 ? 'siteA' : 'siteB', published: i % 3 === 0, isTop: i % 5 === 0, isRecommend: i % 4 === 0, sort: i + 1 }))
|
||||
pagination.total = tableData.value.length
|
||||
fetch('http://desktop-2t92coa.local:3000/api/wechat?page=' + pagination.currentPage + '&pageSize=' + pagination.pageSize + '&' + buildSearchParams(), {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
tableData.value = data.data
|
||||
pagination.total = data.pagination.total
|
||||
loading.value = false
|
||||
}, 400)
|
||||
}).catch(error => {
|
||||
ElMessage.error('加载失败' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
const buildSearchParams = () => {
|
||||
const params = new URLSearchParams()
|
||||
params.append('isDeleted', 0)
|
||||
if (searchForm.unitName) { params.append('unitName', searchForm.unitName) }
|
||||
if (searchForm.contact) { params.append('contact', searchForm.contact) }
|
||||
if (searchForm.status.length > 0) {
|
||||
searchForm.status.forEach(status => {
|
||||
params.append(status.split('=')[0], status.split('=')[1])
|
||||
})
|
||||
}
|
||||
return params.toString()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
@@ -234,43 +304,276 @@ const handleAdd = () => {
|
||||
// 编辑
|
||||
const handleEdit = (row) => {
|
||||
dialogType.value = 'edit'
|
||||
Object.assign(form, row)
|
||||
const booleanFields = ['isPublished', 'isTop', 'isRecommend', 'isHot', 'isSlideshow', 'isDraft'];
|
||||
const convertedRest = { ...row };
|
||||
booleanFields.forEach(key => {
|
||||
if (key in convertedRest) {
|
||||
convertedRest[key] = convertedRest[key] === 1 ? true : false;
|
||||
}
|
||||
});
|
||||
Object.assign(form, convertedRest)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm(`确定要删除 ${row.unitName} 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessage.success('删除成功'); loadData() })
|
||||
ElMessageBox.confirm(`确定要删除公众号吗?`, '确认删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/wechat/${row.id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('删除成功')
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('删除失败' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = () => {
|
||||
if (!hasSelection.value) { ElMessage.warning('请选择记录'); return }
|
||||
ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 条记录吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessage.success('删除成功'); loadData() })
|
||||
// 上架
|
||||
const handlePublish = (row) => {
|
||||
ElMessageBox.confirm(`确定要上架公众号吗?`, '确认上架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handlePublishWechat([row.id])
|
||||
})
|
||||
}
|
||||
|
||||
// 上架/下架
|
||||
const handlePublish = (row) => { ElMessage.info('上架功能占位'); }
|
||||
const handleUnpublish = (row) => { ElMessage.info('下架功能占位'); }
|
||||
const handleBatchPublish = () => { ElMessage.info('批量上架功能占位'); }
|
||||
const handleBatchUnpublish = () => { ElMessage.info('批量下架功能占位'); }
|
||||
// 下架
|
||||
const handleUnpublish = (row) => {
|
||||
ElMessageBox.confirm(`确定要下架公众号吗?`, '确认下架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handleOfflineWechat([row.id])
|
||||
})
|
||||
}
|
||||
|
||||
// 批量上架
|
||||
const handleBatchPublish = () => {
|
||||
ElMessageBox.confirm(`确定要上架 ${selectedRows.value.length} 个公众号吗?`, '确认上架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handlePublishWechat(selectedRows.value.map(row => row.id))
|
||||
})
|
||||
}
|
||||
|
||||
// 批量下架
|
||||
const handleBatchUnpublish = () => {
|
||||
ElMessageBox.confirm(`确定要下架 ${selectedRows.value.length} 个公众号吗?`, '确认下架', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
handleOfflineWechat(selectedRows.value.map(row => row.id))
|
||||
})
|
||||
}
|
||||
|
||||
// 上架请求
|
||||
const handlePublishWechat = (ids) => {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/wechat/publish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('上架成功')
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('上架失败' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
// 下架请求
|
||||
const handleOfflineWechat = (ids) => {
|
||||
fetch('http://desktop-2t92coa.local:3000/api/wechat/unpublish', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('下架成功')
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('下架失败' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
// 导入/导出
|
||||
const handleImport = () => { ElMessage.info('导入功能占位') }
|
||||
const handleExport = () => { ElMessage.info('导出功能占位') }
|
||||
|
||||
// 提交表单(新增/修改占位)
|
||||
// 提交表单
|
||||
const handleSubmit = () => {
|
||||
if (!formRef.value) return
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||||
dialogVisible.value = false
|
||||
loadData()
|
||||
fetch('http://desktop-2t92coa.local:3000/api/wechat/' + (dialogType.value === 'add' ? 0 : form.id), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(form),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('添加成功')
|
||||
dialogVisible.value = false
|
||||
loadData()
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('添加失败' + error.message)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取状态数组
|
||||
const getArticleStatuses = (row) => {
|
||||
const statuses = []
|
||||
// 基础状态
|
||||
if (row.isDraft === 1) {
|
||||
statuses.push({ key: 'draft', name: '草稿', type: 'info' })
|
||||
} else if (row.isPublished === 1) {
|
||||
statuses.push({ key: 'published', name: '已发布', type: 'success' })
|
||||
} else {
|
||||
statuses.push({ key: 'offline', name: '已下架', type: 'warning' })
|
||||
}
|
||||
// 特殊状态标签
|
||||
if (row.isTop === 1) {
|
||||
statuses.push({ key: 'top', name: '置顶', type: 'danger' })
|
||||
}
|
||||
if (row.isRecommend === 1) {
|
||||
statuses.push({ key: 'recommended', name: '推荐', type: 'primary' })
|
||||
}
|
||||
if (row.isHot === 1) {
|
||||
statuses.push({ key: 'hot', name: '热点', type: 'warning' })
|
||||
}
|
||||
if (row.isSlideshow === 1) {
|
||||
statuses.push({ key: 'slideshow', name: '幻灯', type: 'success' })
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
// 回收站相关
|
||||
const recycleDialogVisible = ref(false)
|
||||
const recycleTableData = ref([])
|
||||
const recycleSearch = ref('')
|
||||
|
||||
const openRecycleBin = () => {
|
||||
recycleDialogVisible.value = true
|
||||
loadRecycleData()
|
||||
}
|
||||
|
||||
// 加载回收站数据
|
||||
const loadRecycleData = () => {
|
||||
recycleTableData.value = []
|
||||
fetch('http://desktop-2t92coa.local:3000/api/wechat?isDeleted=1&unitName=' + recycleSearch.value, {
|
||||
method: 'GET'
|
||||
}).then(response => response.json()).then(data => {
|
||||
recycleTableData.value = data.data
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复回收站公众号
|
||||
const handleRecover = (row) => {
|
||||
ElMessageBox.confirm(`确定恢复公众号吗?`, '确认恢复', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/wechat/recover/${row.id}`, {
|
||||
method: 'POST'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('恢复成功')
|
||||
loadRecycleData()
|
||||
if (recycleTableData.value.length === 0) {
|
||||
recycleDialogVisible.value = false
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('恢复失败' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 永久删除回收站公众号
|
||||
const handleForceDelete = (row) => {
|
||||
ElMessageBox.confirm(`永久删除公众号吗? 此操作不可恢复。`, '永久删除', {
|
||||
confirmButtonText: '删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
fetch(`http://desktop-2t92coa.local:3000/api/wechat/force/${row.id}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data.success) {
|
||||
ElMessage.success('已永久删除')
|
||||
loadRecycleData()
|
||||
if (recycleTableData.value.length === 0) {
|
||||
recycleDialogVisible.value = false
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(data.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('删除失败' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 一键恢复全部
|
||||
const handleRecoverAll = () => {
|
||||
ElMessageBox.confirm('确定一键恢复回收站中的全部公众号吗?', '确认恢复全部', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量恢复
|
||||
ElMessage.success('一键恢复(占位)')
|
||||
loadRecycleData()
|
||||
loadData()
|
||||
})
|
||||
}
|
||||
|
||||
// 一键删除全部
|
||||
const handleForceDeleteAll = () => {
|
||||
ElMessageBox.confirm('确定要永久删除回收站中的全部公众号吗?此操作不可恢复。', '永久删除全部', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
// 实际实现调用 API 批量永久删除
|
||||
ElMessage.success('一键删除(占位)')
|
||||
loadRecycleData()
|
||||
})
|
||||
}
|
||||
|
||||
// 监控:当回收站弹窗关闭时,加载数据
|
||||
watch(recycleDialogVisible, (newVal) => {
|
||||
if (!newVal) {
|
||||
loadData()
|
||||
}
|
||||
})
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (selection) => { selectedRows.value = selection }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user