瀏覽代碼

修改消息通知 内容为富文本

hurx 17 小時之前
父節點
當前提交
b7a540e5b3

+ 244 - 0
src/components/Editor/index.vue

@@ -0,0 +1,244 @@
+<template>
+  <div>
+    <el-upload
+      v-if="type === 'url'"
+      :action="upload.url"
+      :before-upload="handleBeforeUpload"
+      :on-success="handleUploadSuccess"
+      :on-error="handleUploadError"
+      class="editor-img-uploader"
+      name="file"
+      :show-file-list="false"
+      :headers="upload.headers"
+    >
+      <i ref="uploadRef"></i>
+    </el-upload>
+  </div>
+  <div class="editor">
+    <quill-editor
+      ref="quillEditorRef"
+      v-model:content="content"
+      content-type="html"
+      :options="options"
+      :style="styles"
+      @text-change="(e: any) => $emit('update:modelValue', content)"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import '@vueup/vue-quill/dist/vue-quill.snow.css';
+
+import { QuillEditor, Quill } from '@vueup/vue-quill';
+import { propTypes } from '@/utils/propTypes';
+import { globalHeaders } from '@/utils/request';
+
+defineEmits(['update:modelValue']);
+
+const props = defineProps({
+  /* 编辑器的内容 */
+  modelValue: propTypes.string,
+  /* 高度 */
+  height: propTypes.number.def(400),
+  /* 最小高度 */
+  minHeight: propTypes.number.def(400),
+  /* 只读 */
+  readOnly: propTypes.bool.def(false),
+  /* 上传文件大小限制(MB) */
+  fileSize: propTypes.number.def(5),
+  /* 类型(base64格式、url格式) */
+  type: propTypes.string.def('url')
+});
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const upload = reactive<UploadOption>({
+  headers: globalHeaders(),
+  url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
+});
+const quillEditorRef = ref();
+const uploadRef = ref<HTMLDivElement>();
+
+const options = ref<any>({
+  theme: 'snow',
+  bounds: document.body,
+  debug: 'warn',
+  modules: {
+    // 工具栏配置
+    toolbar: {
+      container: [
+        ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
+        ['blockquote', 'code-block'], // 引用  代码块
+        [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
+        [{ indent: '-1' }, { indent: '+1' }], // 缩进
+        [{ size: ['small', false, 'large', 'huge'] }], // 字体大小
+        [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
+        [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
+        [{ align: [] }], // 对齐方式
+        ['clean'], // 清除文本格式
+        ['link', 'image', 'video'] // 链接、图片、视频
+      ],
+      handlers: {
+        image: (value: boolean) => {
+          if (value) {
+            // 调用element图片上传
+            uploadRef.value.click();
+          } else {
+            Quill.format('image', true);
+          }
+        }
+      }
+    }
+  },
+  placeholder: '请输入内容',
+  readOnly: props.readOnly
+});
+
+const styles = computed(() => {
+  const style: any = {};
+  if (props.minHeight) {
+    style.minHeight = `${props.minHeight}px`;
+  }
+  if (props.height) {
+    style.height = `${props.height}px`;
+  }
+  return style;
+});
+
+const content = ref('');
+watch(
+  () => props.modelValue,
+  (v: string) => {
+    if (v !== content.value) {
+      content.value = v || '<p></p>';
+    }
+  },
+  { immediate: true }
+);
+
+// 图片上传成功返回图片地址
+const handleUploadSuccess = (res: any) => {
+  // 如果上传成功
+  if (res.code === 200) {
+    // 获取富文本实例
+    const quill = toRaw(quillEditorRef.value).getQuill();
+    // 获取光标位置
+    const length = quill.selection.savedRange.index;
+    // 插入图片,res为服务器返回的图片链接地址
+    quill.insertEmbed(length, 'image', res.data.url);
+    // 调整光标到最后
+    quill.setSelection(length + 1);
+    proxy?.$modal.closeLoading();
+  } else {
+    proxy?.$modal.msgError('图片插入失败');
+    proxy?.$modal.closeLoading();
+  }
+};
+
+// 图片上传前拦截
+const handleBeforeUpload = (file: any) => {
+  const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
+  const isJPG = type.includes(file.type);
+  //检验文件格式
+  if (!isJPG) {
+    proxy?.$modal.msgError(`图片格式错误!`);
+    return false;
+  }
+  // 校检文件大小
+  if (props.fileSize) {
+    const isLt = file.size / 1024 / 1024 < props.fileSize;
+    if (!isLt) {
+      proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
+      return false;
+    }
+  }
+  proxy?.$modal.loading('正在上传文件,请稍候...');
+  return true;
+};
+
+// 图片失败拦截
+const handleUploadError = (err: any) => {
+  proxy?.$modal.msgError('上传文件失败');
+};
+</script>
+
+<style>
+.editor-img-uploader {
+  display: none;
+}
+.editor,
+.ql-toolbar {
+  white-space: pre-wrap !important;
+  line-height: normal !important;
+}
+.quill-img {
+  display: none;
+}
+.ql-snow .ql-tooltip[data-mode='link']::before {
+  content: '请输入链接地址:';
+}
+.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
+  border-right: 0;
+  content: '保存';
+  padding-right: 0;
+}
+.ql-snow .ql-tooltip[data-mode='video']::before {
+  content: '请输入视频地址:';
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item::before {
+  content: '14px';
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
+  content: '10px';
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
+  content: '18px';
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
+  content: '32px';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item::before {
+  content: '文本';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
+  content: '标题1';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
+  content: '标题2';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
+  content: '标题3';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
+  content: '标题4';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
+  content: '标题5';
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
+  content: '标题6';
+}
+.ql-snow .ql-picker.ql-font .ql-picker-label::before,
+.ql-snow .ql-picker.ql-font .ql-picker-item::before {
+  content: '标准字体';
+}
+.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
+.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
+  content: '衬线字体';
+}
+.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
+.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
+  content: '等宽字体';
+}
+</style>

+ 6 - 1
src/router/index.ts

@@ -356,7 +356,12 @@ export const constantRoutes: RouteRecordRaw[] = [
         component: () => import('@/views/enterprise/messageNotice/index.vue'),
         meta: { title: '消息通知', workbench: true }
       },
-
+      {
+        path: 'enterprise/messageNoticeAdd',
+        name: 'MessageNoticeAdd',
+        component: () => import('@/views/enterprise/messageNotice/addNotice.vue'),
+        meta: { title: '新增消息通知', hidden: true, workbench: true }
+      },
       {
         path: 'enterprise/invoiceManage',
         name: 'InvoiceManage',

+ 1 - 0
src/utils/siteConfig.ts

@@ -88,6 +88,7 @@ export const SITE_ROUTES: Record<any, string[]> = {
     '/enterprise/securitySetting/resetPassword',
     '/enterprise/securitySetting/changePhone',
     '/enterprise/changePerson',
+    '/enterprise/messageNoticeAdd',
     '/order/orderEvaluation/evaluation'
   ], //订单列表
 

+ 126 - 0
src/views/enterprise/messageNotice/addNotice.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="message-notice-container">
+    <div class="page-title">
+      <i class="title-bar"></i>
+      <span>{{ isEdit ? '编辑消息通知' : '新增消息通知' }}</span>
+    </div>
+    <div class="form-wrapper">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="form.title" placeholder="请输入标题" />
+        </el-form-item>
+        <el-form-item label="内容" prop="content">
+          <Editor v-model="form.content" :height="400" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio label="0">生效</el-radio>
+            <el-radio label="1">停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleCancel">取消</el-button>
+          <el-button type="danger" @click="handleSave">确定</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import Editor from '@/components/Editor/index.vue';
+import { ref, reactive, onMounted } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import { getMessageInfo, addMessage, updateMessage } from '@/api/pc/enterprise';
+
+const router = useRouter();
+const route = useRoute();
+const formRef = ref();
+
+const form = reactive({
+  title: '',
+  content: '',
+  status: '0'
+});
+
+const rules = {
+  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
+};
+
+// 判断是否为编辑模式
+const isEdit = ref(false);
+
+onMounted(async () => {
+  const id = route.query.id as any;
+  if (id) {
+    isEdit.value = true;
+    try {
+      const res = await getMessageInfo(id);
+      if (res.code === 200 && res.data) {
+        form.title = res.data.title;
+        form.content = res.data.content;
+        form.status = String(res.data.status);
+      }
+    } catch (error) {
+      ElMessage.error('获取消息通知详情失败');
+    }
+  }
+});
+
+const handleCancel = () => {
+  router.back();
+};
+
+const handleSave = async () => {
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
+  try {
+    const data: any = {
+      title: form.title,
+      content: form.content,
+      status: form.status
+    };
+
+    if (isEdit.value) {
+      data.id = Number(route.query.id);
+      await updateMessage(data);
+      ElMessage.success('修改成功');
+    } else {
+      await addMessage(data);
+      ElMessage.success('新增成功');
+    }
+    router.back();
+  } catch (error) {
+    ElMessage.error('操作失败');
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.message-notice-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+  flex: 1;
+}
+.page-title {
+  font-size: 16px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 20px;
+}
+.title-bar {
+  display: inline-block;
+  width: 3px;
+  height: 16px;
+  background: #e60012;
+  border-radius: 2px;
+}
+.form-wrapper {
+  margin-top: 30px;
+}
+</style>

+ 8 - 103
src/views/enterprise/messageNotice/index.vue

@@ -79,111 +79,27 @@
         @current-change="handleQuery"
       />
     </div>
-    <!-- 新增/编辑弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="550px">
-      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
-        <el-form-item label="标题" prop="title">
-          <el-input v-model="form.title" placeholder="请输入标题" />
-        </el-form-item>
-        <el-form-item label="内容" prop="content">
-          <el-input v-model="form.content" type="textarea" :rows="6" :maxlength="300" show-word-limit placeholder="请输入内容" />
-        </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-radio-group v-model="form.status">
-            <el-radio label="0">生效</el-radio>
-            <el-radio label="1">停用</el-radio>
-          </el-radio-group>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="handleSave">确定</el-button>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue';
-import { Document, Bell, Warning, User, Box } from '@element-plus/icons-vue';
-import { ElMessage } from 'element-plus';
-import { PageTitle, TablePagination } from '@/components';
-import { getMessageList, getMessageInfo, addMessage, updateMessage, deleteMessage } from '@/api/pc/enterprise';
+import { ref, reactive, onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { getMessageList, deleteMessage } from '@/api/pc/enterprise';
 
-const activeTab = ref('approval');
-const dialogVisible = ref(false);
-const dialogTitle = ref('新增消息通知');
-const formRef = ref();
-const editingId = ref<number | null>(null);
-const form = reactive({ title: '', content: '', status: '0' });
-
-const rules = {
-  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
-};
-const tabs = [
-  { key: 'approval', label: '审批待办', icon: Document },
-  { key: 'arrival', label: '到货提醒', icon: Bell },
-  { key: 'budget', label: '预算预警', icon: Warning }
-];
-
-const resetForm = () => {
-  form.title = '';
-  form.content = '';
-  editingId.value = null;
-};
+const router = useRouter();
 const queryParams = reactive({ pageNum: 1, pageSize: 10 });
 const total = ref(0);
 const messageList = ref<any[]>([]);
-const loadData = () => {
-  if (activeTab.value === 'approval') {
-    messageList.value = [];
-    total.value = 0;
-  } else if (activeTab.value === 'arrival') {
-    messageList.value = [];
-    total.value = 0;
-  } else {
-    messageList.value = [];
-    total.value = 0;
-  }
-};
+
 const handleAdd = () => {
-  resetForm();
-  dialogTitle.value = '新增消息通知';
-  dialogVisible.value = true;
+  router.push('/enterprise/messageNoticeAdd');
 };
 const handleEdit = (item: any) => {
-  editingId.value = item.id;
-  form.title = item.title;
-  form.content = item.content;
-  form.status = item.status;
-
-  dialogTitle.value = '编辑消息通知';
-  dialogVisible.value = true;
+  router.push(`/enterprise/messageNoticeAdd?id=${item.id}`);
 };
-const handleSave = async () => {
-  const valid = await formRef.value?.validate();
-  if (!valid) return;
-  try {
-    const data: any = {
-      title: form.title,
-      content: form.content,
-      status: form.status
-    };
 
-    if (editingId.value) {
-      data.id = editingId.value;
-      await updateMessage(data);
-    } else {
-      await addMessage(data);
-    }
-    ElMessage.success(editingId.value ? '修改成功' : '新增成功');
-    dialogVisible.value = false;
-    loadMessageList();
-  } catch (error) {
-    ElMessage.error('操作失败');
-  }
-};
 const handleDelete = (item: any) => {
   ElMessageBox.confirm('确定要删除该消息通知吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(
     async () => {
@@ -215,20 +131,9 @@ onMounted(() => {
   loadMessageList();
 });
 
-watch(
-  activeTab,
-  () => {
-    queryParams.pageNum = 1;
-    loadData();
-  },
-  { immediate: true }
-);
 const handleQuery = () => {
   loadMessageList();
 };
-const handleProcess = (_item: any) => {
-  ElMessage.info('跳转到审批处理页面');
-};
 </script>
 
 <style scoped lang="scss">