This commit is contained in:
李远 2026-03-19 13:13:33 +08:00
parent 2a83395858
commit c9f943f8dd
5 changed files with 209 additions and 53 deletions

View File

@ -6,5 +6,13 @@ export const getNoticeListApi = (params: any) => {
} }
//创建公告 //创建公告
export const createNoticeApi = (params: any) => { export const createNoticeApi = (params: any) => {
return request.get('/notice/createNotice', {...params}) return request.post('/notice/createNotice', {...params})
} }
//编辑草稿
export const editNoticeApi = (params: any) => {
return request.post('/notice/editNotice', {...params})
}
//删除草稿
export const deleteNoticeApi = (params: any) => {
return request.post('/notice/delNotice', {...params})
}

27
src/api/modules/upload.ts Normal file
View File

@ -0,0 +1,27 @@
import { request } from '@/api'; // 引入你已封装的request
/**
*
* @param file
* @returns Promise<{ url: string }> URL
*/
export const uploadImageApi = (file: File): Promise<{ url: string }> => {
// 构建FormData图片上传必须用FormData格式
const formData = new FormData();
formData.append('image', file); // 对应后端接收字段名image
// 使用你已封装的post请求禁用loading富文本自身有状态提示
return request.post<{ url: string }>(
'/upload/image',
formData,
{
// 覆盖默认配置:图片上传需要特殊的请求头
headers: {
'Content-Type': 'multipart/form-data', // 表单上传必须的头
showLoading: false // 禁用全局loading避免和编辑器loading冲突
},
timeout: 30 * 1000, // 图片上传超时时间30秒
noRetry: false // 允许重试(网络波动时自动重试)
}
);
};

View File

@ -7,24 +7,34 @@
<el-button type="primary" @click="handleCreate">新建</el-button> <el-button type="primary" @click="handleCreate">新建</el-button>
</template> </template>
</NoticeSearch> </NoticeSearch>
<div class="tab">
<el-button :type="activeTab === 2 ? 'primary' : 'default'" @click="handleTabChange(2)">已发布</el-button>
<el-button :type="activeTab === 1 ? 'primary' : 'default'" @click="handleTabChange(1)">草稿</el-button>
</div>
<!-- 表格 --> <!-- 表格 -->
<NoticeTable :table-data="noticeTree" :columns="noticeTableColumnsOptions" row-key="id"> <NoticeTable :table-data="noticeTree" :columns="noticeTableColumnsOptions" row-key="id">
<template #status="{ row }"> <template #status="{ row }">
{{ row.status === 1 ? '已发布' : '未发布' }} <!-- 修正状态显示1=草稿2=已发布 -->
{{ row.status === 1 ? '草稿' : '已发布' }}
</template> </template>
<template #action="{ row }"> <template #action="{ row }">
<el-button type="primary" size="small" @click="handleView(row)">查看</el-button> <el-button type="primary" size="small" v-if="row.status === 2" @click="handleView(row)">查看</el-button>
<!-- 草稿才显示编辑/删除已发布仅查看 -->
<template v-if="row.status === 1">
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</template> </template>
</NoticeTable> </NoticeTable>
<!-- 分页 --> <!-- 分页 -->
<NoticePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total" <NoticePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" /> @pagination-change="handlePaginationChange" />
<!-- dialog --> <!-- dialog -->
<NoticeDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit" <NoticeDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :show-footer="dialogType !== 'view'" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose"> @cancel="handleCancel" @close="handleClose">
<NoticeForm :form-data="noticeForm" :form-rules="noticeRules" :form-items="noticeFormItem" <NoticeForm :form-data="noticeForm" :form-rules="noticeRules" :form-items="noticeFormItem"
:options-map="noticeFormOptionsMap" ref="noticeFormRef" v-if="dialogType !== 'view'"> :options-map="noticeFormOptionsMap" ref="noticeFormRef" v-if="dialogType !== 'view'">
<template #noticeContent="{ row }"> <template #noticeContent="{ formData }">
<div style="border: 1px solid #e6e6e6; margin: 10px 0;"> <div style="border: 1px solid #e6e6e6; margin: 10px 0;">
<Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" mode="default" <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" mode="default"
style="border-bottom: 1px solid #ccc;" /> style="border-bottom: 1px solid #ccc;" />
@ -33,7 +43,10 @@
</div> </div>
</template> </template>
</NoticeForm> </NoticeForm>
<div v-html="valueHtml"></div> <div v-else class="view-content">
<h2>公告名称{{ noticeForm.noticeName }}</h2>
<div v-html="noticeForm.noticeContent"></div>
</div>
</NoticeDialog> </NoticeDialog>
<!-- dialog表格 --> <!-- dialog表格 -->
</div> </div>
@ -51,45 +64,55 @@ import { search, tableColumns, formItem } from './options'
import { rules } from './rules' import { rules } from './rules'
// //
import { getNoticeListApi, createNoticeApi } from '@/api/modules/notice' import { getNoticeListApi, createNoticeApi, editNoticeApi, deleteNoticeApi } from '@/api/modules/notice'
import { uploadImageApi } from '@/api/modules/upload'
import { DomEditor } from '@wangeditor/editor' import { DomEditor } from '@wangeditor/editor'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css' import '@wangeditor/editor/dist/css/style.css'
// //
const editorRef = shallowRef() const editorRef = shallowRef()
// DomEditor // DomEditor
const toolbarRef = shallowRef() const toolbarRef = shallowRef()
// HTML // HTML
const valueHtml = ref('') const valueHtml = ref('')
//
const toolbarConfig = { const toolbarConfig = {
excludeKeys: ["group-video"] excludeKeys: ["group-video", "fullScreen"]
} }
const editorConfig = { placeholder: '请输入内容...' } //
const editorConfig = {
// placeholder: '请输入内容...',
onBeforeUnmount(() => { MENU_CONF: {
const editor = editorRef.value uploadImage: {
if (editor == null) return customUpload: async (file: any, insertFn: any) => {
editor.destroy() try {
}) // API
const res = await uploadImageApi(file);
// //
const handleCreated = (editor) => { insertFn(res.url);
editorRef.value = editor } catch (error: any) {
// ElNotification
// const errMsg = error?.message || error?.data?.msg || '图片上传失败';
nextTick(() => { alert(`图片${file.name}上传失败:${errMsg}`);
const toolbar = DomEditor.getToolbar(editor) ElNotification({
if (toolbar) { message: errMsg || '未知的错误!',
const curToolbarConfig = toolbar.getConfig() type: 'error',
console.log('curToolbarConfig.toolbarKeys', curToolbarConfig.toolbarKeys) duration: 2000,
toolbarRef.value = toolbar customClass: 'global-notification'
console.log('toolbarRef.value', valueHtml.value) })
}
},
//
fieldName: 'image',
timeout: 30 * 1000,
maxFileSize: 5 * 1024 * 1024,
allowedFileTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
} }
}) }
} }
/** /**
@ -99,10 +122,15 @@ const searchForm = ref({
noticeName: '', noticeName: '',
startDateTime: '', startDateTime: '',
endDateTime: '', endDateTime: '',
status: 1, status: 2,
}) })
const searchOptions = ref(search) const searchOptions = ref(search)
/**
* @description: 定义tab数据
*/
const activeTab = ref(2)
/** /**
* @description: 定义表格数据 * @description: 定义表格数据
*/ */
@ -131,20 +159,18 @@ const noticeForm = ref({
publishStartTime: '', publishStartTime: '',
publishEndTime: '', publishEndTime: '',
status: 1, status: 1,
id: 0,
}) })
const noticeFormRef = ref() const noticeFormRef = ref()
const noticeRules = ref(rules()) const noticeRules = ref(rules())
const noticeFormItem = ref(formItem) const noticeFormItem = ref(formItem)
const noticeFormOptionsMap = ref({ const noticeFormOptionsMap = ref({
status: [ status: [
{ label: '发布', value: 1 }, { label: '发布', value: 2 },
{ label: '草稿', value: 2 }, { label: '草稿', value: 1 },
], ],
}) })
/**
* @description: 定义公共数据处理方法
*/
/** /**
* @description: 定义请求数据方法 * @description: 定义请求数据方法
*/ */
@ -161,17 +187,16 @@ const getNoticeList = async () => {
const data: any = await getNoticeListApi(params) const data: any = await getNoticeListApi(params)
// //
noticeTree.value = data.data.map((item: any) => ({ noticeTree.value = data.data.map((item: any) => ({
id: item.id,
noticeName: item.name, noticeName: item.name,
noticeContent: item.content, noticeContent: item.content,
startDateTime: item.publish_start_time, publishTime: item.publish_time,
status: item.status, status: item.status,
})) }))
// //
total.value = data.total total.value = data.total
currentPage.value = data.current_page currentPage.value = data.current_page
pageSize.value = Number(data.per_page) pageSize.value = Number(data.per_page)
console.log(noticeTree.value)
} }
/** /**
@ -192,8 +217,9 @@ const handleReset = () => {
noticeName: '', noticeName: '',
startDateTime: '', startDateTime: '',
endDateTime: '', endDateTime: '',
status: 1, status: 2,
} }
activeTab.value = 2
getNoticeList() getNoticeList()
} }
const handleCreate = () => { const handleCreate = () => {
@ -201,14 +227,54 @@ const handleCreate = () => {
dialogTitle.value = '新建公告' dialogTitle.value = '新建公告'
dialogType.value = 'create' dialogType.value = 'create'
} }
/**
* @description: 定义tab方法
*/
const handleTabChange = (tab: number) => {
activeTab.value = tab
searchForm.value.status = tab
currentPage.value = 1
getNoticeList()
}
/** /**
* @description: 定义表格方法 * @description: 定义表格方法
*/ */
const handleView = (row: any) => { const handleView = (row: any) => {
console.log(row)
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = '查看公告' dialogTitle.value = '查看公告'
dialogType.value = 'view' dialogType.value = 'view'
noticeForm.value = {
noticeName: row.noticeName,
noticeContent: row.noticeContent,
} as any
} }
const handleEdit = (row: any) => {
dialogVisible.value = true
dialogTitle.value = '编辑公告'
dialogType.value = 'edit'
noticeForm.value = {
noticeName: row.noticeName,
noticeContent: row.noticeContent,
publishStartTime: row.startDateTime,
publishEndTime: row.endDateTime,
status: row.status,
id: row.id,
}
}
//
const handleDelete = async (row: any) => {
const params = {
id: row.id,
}
await deleteNoticeApi(params)
getNoticeList()
}
/** /**
* @description: 定义分页方法 * @description: 定义分页方法
*/ */
@ -221,24 +287,79 @@ const handlePaginationChange = async (page: { currentPage: number, pageSize: num
* @description: 定义弹窗方法 * @description: 定义弹窗方法
*/ */
const handleSubmit = async () => { const handleSubmit = async () => {
await noticeFormRef.value.validate()
if (dialogType.value === 'create') { if (dialogType.value === 'create') {
noticeForm.value.noticeContent = valueHtml.value
const params = { const params = {
name: noticeForm.value.noticeName, name: noticeForm.value.noticeName,
content: noticeForm.value.noticeContent, content: noticeForm.value.noticeContent,
status: noticeForm.value.status, status: noticeForm.value.status,
} }
await createNoticeApi(params) await createNoticeApi(params)
dialogVisible.value = false
getNoticeList()
}else {
noticeForm.value.noticeContent = valueHtml.value
const params = {
id: noticeForm.value.id,
name: noticeForm.value.noticeName,
content: noticeForm.value.noticeContent,
status: noticeForm.value.status,
}
await editNoticeApi(params)
dialogVisible.value = false
getNoticeList()
} }
dialogVisible.value = false
getNoticeList()
} }
const handleCancel = () => { const handleCancel = () => {
dialogVisible.value = false dialogVisible.value = false
} }
const handleClose = () => { const handleClose = () => {
dialogVisible.value = false dialogVisible.value = false
} }
/** /**
* @description: 定义弹窗表格方法 * @description: 定义富文本编辑器方法
*/ */
</script> //
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
//
const handleCreated = (editor: any) => {
editorRef.value = editor
//
nextTick(() => {
const toolbar = DomEditor.getToolbar(editor)
if (toolbar) {
const curToolbarConfig = toolbar.getConfig()
console.log('curToolbarConfig.toolbarKeys', curToolbarConfig.toolbarKeys)
toolbarRef.value = toolbar
console.log('toolbarRef.value', valueHtml.value)
}
})
}
</script>
<style scoped>
.tab {
display: flex;
justify-content: center;
margin-bottom: 10px;
.el-button {
margin-left: 0px;
}
}
.view-content {
h2{
margin-bottom: 10px;
}
}
</style>

View File

@ -2,7 +2,7 @@
* @description: * @description:
*/ */
export const search = [ export const search = [
{ prop: 'username', label: '公告名称', type: 'input' as const, placeholder: '请输入公告名称', width: '120px' }, { prop: 'noticeName', label: '公告名称', type: 'input' as const, placeholder: '请输入公告名称', width: '120px' },
{ {
prop: "startDateTime", label: '发布时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器 prop: "startDateTime", label: '发布时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
@ -20,8 +20,8 @@ export const search = [
*/ */
export const tableColumns = [ export const tableColumns = [
{ prop: 'noticeName', label: '公告名称'}, { prop: 'noticeName', label: '公告名称'},
{ prop: 'noticeContent', label: '公告内容'}, // { prop: 'noticeContent', label: '公告内容'},
{ prop: 'startDateTime', label: '发布时间'}, { prop: 'publishTime', label: '发布时间'},
{ slotName: 'status', label: '状态'}, { slotName: 'status', label: '状态'},
{ slotName: 'action', label: '操作'}, { slotName: 'action', label: '操作'},
] ]
@ -32,6 +32,6 @@ export const tableColumns = [
export const formItem = [ export const formItem = [
{ prop: 'noticeName', label: '公告名称', type: 'input' as const, placeholder: '请输入公告名称'}, { prop: 'noticeName', label: '公告名称', type: 'input' as const, placeholder: '请输入公告名称'},
// 公告内容 // 公告内容
{ slotName: 'noticeContent',label: '公告内容', type: 'edit' as const}, { slotName: 'noticeContent', label: '公告内容', type: 'editor' as const},
{ prop: 'status', label: '状态', type: 'radio' as const, placeholder: '请选择状态'}, { prop: 'status', label: '状态', type: 'radio' as const, placeholder: '请选择状态'},
] ]

View File

@ -1,7 +1,7 @@
import type { FormRules } from 'element-plus' import type { FormRules } from 'element-plus'
export const rules = (): FormRules => { export const rules = (): FormRules => {
return { return {
name: [ noticeName: [
{ required: true, message: '请输入名称', trigger: ['blur'] } { required: true, message: '请输入名称', trigger: ['blur'] }
], ],
} }