576 lines
15 KiB
Vue
576 lines
15 KiB
Vue
<template>
|
||
<view class="container" style="padding-bottom:1rpx;">
|
||
<u-navbar
|
||
leftIconSize="40rpx"
|
||
leftIconColor="#333333"
|
||
:title="title"
|
||
>
|
||
</u-navbar>
|
||
<view class="section">
|
||
<view class="section-title">公告信息</view>
|
||
<view class="form-view">
|
||
<u--form labelPosition="left" :model="form" ref="uForm" labelWidth="160rpx"
|
||
:labelStyle="{ color: '#333333', fontSize: '30rpx' }">
|
||
<u-form-item label="公告标题" prop="noticeTitle" required>
|
||
<u--input v-model="form.noticeTitle" placeholder="请输入公告标题"
|
||
inputAlign="left" :customStyle="getInputStyle('noticeTitle')"></u--input>
|
||
</u-form-item>
|
||
|
||
<u-form-item label="公告类型" prop="noticeType" required @click="showNoticeTypePicker = true" class="with-arrow">
|
||
<u--input v-model="noticeTypeName" disabled disabledColor="#ffffff" placeholder="请选择公告类型"
|
||
inputAlign="left" :customStyle="inputBaseStyle"></u--input>
|
||
</u-form-item>
|
||
|
||
<u-form-item label="状态" prop="status" @click="showStatusPicker = true" class="with-arrow">
|
||
<u--input v-model="statusName" disabled disabledColor="#ffffff" placeholder="请选择状态"
|
||
inputAlign="left" :customStyle="inputBaseStyle"></u--input>
|
||
</u-form-item>
|
||
<u-form-item label="内容" prop="noticeContent" labelPosition="top">
|
||
<view class="editor-container">
|
||
<view class="editor-toolbar">
|
||
<view class="toolbar-item" @click="format('bold')">
|
||
<text class="iconfont">B</text>
|
||
</view>
|
||
<view class="toolbar-item" @click="format('italic')">
|
||
<text class="iconfont italic">I</text>
|
||
</view>
|
||
<view class="toolbar-item" @click="format('underline')">
|
||
<text class="iconfont underline">U</text>
|
||
</view>
|
||
<view class="toolbar-divider"></view>
|
||
<view class="toolbar-item" @click="insertImage">
|
||
<uni-icons type="image" size="16" color="#333333"></uni-icons>
|
||
</view>
|
||
<view class="toolbar-divider"></view>
|
||
<view class="toolbar-item" @click="undo">
|
||
<text class="iconfont">↶</text>
|
||
</view>
|
||
<view class="toolbar-item" @click="redo">
|
||
<text class="iconfont">↷</text>
|
||
</view>
|
||
</view>
|
||
<editor
|
||
id="editor"
|
||
class="editor-content"
|
||
:placeholder="'请输入公告内容'"
|
||
@ready="onEditorReady"
|
||
@input="onEditorInput"
|
||
@focus="editorFocus"
|
||
@blur="editorBlur"
|
||
:show-img-size="true"
|
||
:show-img-toolbar="true"
|
||
:show-img-resize="true"
|
||
></editor>
|
||
<view class="editor-counter">
|
||
<text>{{ contentLength }}/20000</text>
|
||
</view>
|
||
</view>
|
||
</u-form-item>
|
||
</u--form>
|
||
<view class="form-btn">
|
||
<u-button type="primary" text="保存" @click="submitForm"></u-button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 公告类型选择器 -->
|
||
<u-picker itemHeight="88" :show="showNoticeTypePicker" :columns="noticeTypeColumns" keyName="dictLabel"
|
||
@cancel="showNoticeTypePicker = false" @confirm="handleNoticeTypeConfirm"></u-picker>
|
||
|
||
<!-- 状态选择器 -->
|
||
<u-picker itemHeight="88" :show="showStatusPicker" :columns="statusColumns" keyName="dictLabel"
|
||
@cancel="showStatusPicker = false" @confirm="handleStatusConfirm"></u-picker>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||
import { getNotice, addNotice, updateNotice } from '@/api/system/notice'
|
||
import { getDicts } from "@/api/system/dict/data"
|
||
import { onLoad, onReady } from "@dcloudio/uni-app"
|
||
import modal from '@/plugins/modal'
|
||
|
||
const title = ref('新增公告')
|
||
const noticeId = ref(undefined)
|
||
const noticeTypeList = ref([])
|
||
const statusList = ref([])
|
||
const noticeTypeColumns = ref([])
|
||
const statusColumns = ref([])
|
||
const showNoticeTypePicker = ref(false)
|
||
const showStatusPicker = ref(false)
|
||
const noticeTypeName = ref('')
|
||
const statusName = ref('')
|
||
const editorCtx = ref(null)
|
||
const contentLength = ref(0)
|
||
|
||
const form = reactive({
|
||
noticeId: undefined,
|
||
noticeTitle: '',
|
||
noticeType: '',
|
||
noticeContent: '',
|
||
status: '0'
|
||
})
|
||
|
||
// 表单验证错误字段
|
||
const errorFields = ref([])
|
||
|
||
// 输入框基础样式
|
||
const inputBaseStyle = {
|
||
background: '#ffffff',
|
||
border: '2rpx solid #dcdfe6',
|
||
borderRadius: '8rpx',
|
||
padding: '0 24rpx',
|
||
height: '68rpx',
|
||
width: '100%',
|
||
boxSizing: 'border-box'
|
||
}
|
||
|
||
// 输入框错误样式
|
||
const inputErrorStyle = {
|
||
background: '#fef0f0',
|
||
border: '2rpx solid #f56c6c',
|
||
borderRadius: '8rpx',
|
||
padding: '0 24rpx',
|
||
height: '68rpx',
|
||
width: '100%',
|
||
boxSizing: 'border-box'
|
||
}
|
||
|
||
// 根据字段名获取输入框样式
|
||
const getInputStyle = (field) => {
|
||
return errorFields.value.includes(field) ? inputErrorStyle : inputBaseStyle
|
||
}
|
||
|
||
onLoad((options) => {
|
||
if (options.id) {
|
||
noticeId.value = options.id
|
||
title.value = '修改公告'
|
||
getNotice(noticeId.value).then(response => {
|
||
Object.assign(form, response.data)
|
||
// 更新显示名称
|
||
updateNoticeTypeName()
|
||
updateStatusName()
|
||
// 如果编辑器已经初始化,设置内容
|
||
if (editorCtx.value && form.noticeContent) {
|
||
editorCtx.value.setContents({
|
||
html: form.noticeContent
|
||
})
|
||
}
|
||
}).catch(error => {
|
||
console.error('获取公告详情失败:', error)
|
||
})
|
||
}
|
||
|
||
// 获取字典数据
|
||
getDicts('sys_notice_type').then(res => {
|
||
noticeTypeList.value = res.data
|
||
noticeTypeColumns.value = [res.data]
|
||
// 如果是新增模式,设置默认值
|
||
if (!noticeId.value && res.data.length > 0) {
|
||
form.noticeType = res.data[0].dictValue
|
||
noticeTypeName.value = res.data[0].dictLabel
|
||
}
|
||
}).catch(error => {
|
||
console.error('获取公告类型字典失败:', error)
|
||
})
|
||
|
||
getDicts('sys_notice_status').then(res => {
|
||
statusList.value = res.data
|
||
statusColumns.value = [res.data]
|
||
// 更新状态显示
|
||
updateStatusName()
|
||
}).catch(error => {
|
||
console.error('获取公告状态字典失败:', error)
|
||
})
|
||
})
|
||
|
||
// 更新公告类型显示名称
|
||
function updateNoticeTypeName() {
|
||
if (noticeTypeList.value.length > 0) {
|
||
const item = noticeTypeList.value.find(v => v.dictValue === form.noticeType)
|
||
noticeTypeName.value = item ? item.dictLabel : ''
|
||
}
|
||
}
|
||
|
||
// 更新状态显示名称
|
||
function updateStatusName() {
|
||
if (statusList.value.length > 0) {
|
||
const item = statusList.value.find(v => v.dictValue === form.status)
|
||
statusName.value = item ? item.dictLabel : ''
|
||
}
|
||
}
|
||
|
||
// 公告类型选择确认
|
||
function handleNoticeTypeConfirm(e) {
|
||
form.noticeType = e.value[0].dictValue
|
||
noticeTypeName.value = e.value[0].dictLabel
|
||
showNoticeTypePicker.value = false
|
||
}
|
||
|
||
// 状态选择确认
|
||
function handleStatusConfirm(e) {
|
||
form.status = e.value[0].dictValue
|
||
statusName.value = e.value[0].dictLabel
|
||
showStatusPicker.value = false
|
||
}
|
||
|
||
onReady(() => {
|
||
// 页面准备完成后的一些初始化操作
|
||
})
|
||
|
||
// 编辑器初始化完成
|
||
function onEditorReady() {
|
||
uni.createSelectorQuery().select('#editor').context((res) => {
|
||
editorCtx.value = res.context
|
||
// 如果是编辑模式,设置初始内容
|
||
if (form.noticeContent) {
|
||
editorCtx.value.setContents({
|
||
html: form.noticeContent
|
||
})
|
||
}
|
||
}).exec()
|
||
}
|
||
|
||
// 编辑器内容变化
|
||
function onEditorInput(e) {
|
||
// 获取HTML内容
|
||
editorCtx.value.getContents({
|
||
success: (res) => {
|
||
// 使用 Vue 的响应式更新方式
|
||
form.noticeContent = res.html
|
||
// 计算内容长度(去除HTML标签)
|
||
const text = res.text || ''
|
||
contentLength.value = text.length
|
||
}
|
||
})
|
||
}
|
||
|
||
// 编辑器获得焦点
|
||
function editorFocus() {
|
||
// 可以添加焦点样式
|
||
}
|
||
|
||
// 编辑器失去焦点
|
||
function editorBlur() {
|
||
// 保存内容
|
||
if (editorCtx.value) {
|
||
editorCtx.value.getContents({
|
||
success: (res) => {
|
||
form.noticeContent = res.html
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 格式化文本
|
||
function format(name, value) {
|
||
if (!editorCtx.value) return
|
||
|
||
editorCtx.value.format(name, value)
|
||
}
|
||
|
||
// 撤销
|
||
function undo() {
|
||
if (!editorCtx.value) return
|
||
editorCtx.value.undo()
|
||
}
|
||
|
||
// 重做
|
||
function redo() {
|
||
if (!editorCtx.value) return
|
||
editorCtx.value.redo()
|
||
}
|
||
|
||
// 插入图片
|
||
function insertImage() {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: (res) => {
|
||
const tempFilePath = res.tempFilePaths[0]
|
||
|
||
// 显示加载提示
|
||
uni.showLoading({
|
||
title: '上传中...'
|
||
})
|
||
|
||
// 这里需要根据实际的上传接口进行调整
|
||
// 示例代码,实际使用时需要替换为真实的上传逻辑
|
||
uni.uploadFile({
|
||
url: '/common/upload', // 替换为实际的上传地址
|
||
filePath: tempFilePath,
|
||
name: 'file',
|
||
success: (uploadRes) => {
|
||
uni.hideLoading()
|
||
try {
|
||
const data = JSON.parse(uploadRes.data)
|
||
if (data.code === 200) {
|
||
// 获取图片URL - 需要根据实际返回结构调整
|
||
const imageUrl = data.url || data.data || data.fileName
|
||
|
||
// 插入图片到编辑器
|
||
if (editorCtx.value) {
|
||
editorCtx.value.insertImage({
|
||
src: imageUrl,
|
||
width: '100%',
|
||
height: 'auto',
|
||
success: () => {
|
||
uni.showToast({
|
||
title: '插入成功',
|
||
icon: 'success'
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
console.error('插入图片失败', err)
|
||
uni.showToast({
|
||
title: '插入失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
} else {
|
||
uni.showToast({
|
||
title: data.msg || '上传失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.error('解析上传结果失败', e)
|
||
uni.showToast({
|
||
title: '上传失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('上传失败', err)
|
||
uni.showToast({
|
||
title: '上传失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
console.error('选择图片失败', err)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 提交表单
|
||
async function submitForm() {
|
||
try {
|
||
// 提交前确保获取最新的编辑器内容
|
||
if (editorCtx.value) {
|
||
const content = await new Promise((resolve, reject) => {
|
||
editorCtx.value.getContents({
|
||
success: (res) => {
|
||
form.noticeContent = res.html
|
||
resolve(res.html)
|
||
},
|
||
fail: reject
|
||
})
|
||
})
|
||
|
||
// 确保内容已更新到表单
|
||
await nextTick()
|
||
}
|
||
|
||
doSubmit()
|
||
} catch (error) {
|
||
modal.msgError('获取内容失败,请重试')
|
||
}
|
||
}
|
||
|
||
function doSubmit() {
|
||
errorFields.value = [] // 清空错误字段
|
||
|
||
if (!form.noticeTitle) {
|
||
errorFields.value.push('noticeTitle')
|
||
modal.msgError('请输入公告标题')
|
||
return
|
||
}
|
||
if (!form.noticeType) {
|
||
errorFields.value.push('noticeType')
|
||
modal.msgError('请选择公告类型')
|
||
return
|
||
}
|
||
if (!form.noticeContent || form.noticeContent.trim() === '') {
|
||
errorFields.value.push('noticeContent')
|
||
modal.msgError('请输入公告内容')
|
||
return
|
||
}
|
||
|
||
const submitForm = { ...form }
|
||
|
||
if (noticeId.value) {
|
||
updateNotice(submitForm).then(response => {
|
||
modal.msgSuccess('修改成功')
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1000)
|
||
}).catch(error => {
|
||
modal.msgError('修改失败: ' + (error.message || '未知错误'))
|
||
})
|
||
} else {
|
||
addNotice(submitForm).then(response => {
|
||
modal.msgSuccess('新增成功')
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1000)
|
||
}).catch(error => {
|
||
modal.msgError('新增失败: ' + (error.message || '未知错误'))
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.container {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.section {
|
||
margin: 24rpx;
|
||
padding: 0;
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||
overflow: hidden;
|
||
|
||
.section-title {
|
||
padding: 16rpx 24rpx;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: #ffffff;
|
||
line-height: 1.2;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
&::before {
|
||
content: '';
|
||
width: 6rpx;
|
||
height: 28rpx;
|
||
background: #ffffff;
|
||
border-radius: 3rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
}
|
||
|
||
.form-view {
|
||
padding: 24rpx;
|
||
|
||
.form-btn {
|
||
padding-top: 16rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.editor-container {
|
||
width: 100%;
|
||
border: 2rpx solid #e8edf3;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
background: #ffffff;
|
||
box-sizing: border-box;
|
||
|
||
.editor-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12rpx;
|
||
background: #f8f9fa;
|
||
border-bottom: 2rpx solid #e8edf3;
|
||
flex-wrap: wrap;
|
||
gap: 8rpx;
|
||
|
||
.toolbar-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
background: #ffffff;
|
||
border-radius: 8rpx;
|
||
border: 2rpx solid #e8edf3;
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
background: #667eea;
|
||
border-color: #667eea;
|
||
|
||
.iconfont {
|
||
color: #ffffff;
|
||
}
|
||
|
||
uni-icons {
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
|
||
.iconfont {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
|
||
&.italic {
|
||
font-style: italic;
|
||
}
|
||
|
||
&.underline {
|
||
text-decoration: underline;
|
||
}
|
||
}
|
||
}
|
||
|
||
.toolbar-divider {
|
||
width: 2rpx;
|
||
height: 40rpx;
|
||
background: #e8edf3;
|
||
margin: 0 4rpx;
|
||
}
|
||
}
|
||
|
||
.editor-content {
|
||
width: 100%;
|
||
min-height: 500rpx;
|
||
padding: 20rpx;
|
||
font-size: 28rpx;
|
||
line-height: 1.6;
|
||
background: #ffffff;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.editor-counter {
|
||
padding: 12rpx 20rpx;
|
||
background: #f8f9fa;
|
||
border-top: 2rpx solid #e8edf3;
|
||
text-align: right;
|
||
|
||
text {
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<style lang="scss">
|
||
.form-btn .u-button {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
||
border: none !important;
|
||
border-radius: 24rpx !important;
|
||
height: 80rpx !important;
|
||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4) !important;
|
||
}
|
||
|
||
.form-btn .u-button__text {
|
||
font-size: 30rpx !important;
|
||
font-weight: 500 !important;
|
||
letter-spacing: 2rpx !important;
|
||
}
|
||
</style> |