Преглед на файлове

🎨 保存按钮和富文本编辑器修改

快乐的梦鱼 преди 6 дни
родител
ревизия
70b9278eea

+ 0 - 2
src/components/display/SimpleRichHtml.vue

@@ -115,8 +115,6 @@ onBeforeUnmount(() => {
   }
 })
 onMounted(() => {
-  if (import.meta.server)
-    return;
   genTagCss()
 });
 </script>

+ 97 - 30
src/components/dynamicf/Editor/QuillEditorWrapper.vue

@@ -2,7 +2,8 @@
   <div class="quill-editor-wrapper">
     <QuillEditor
       :modules="modules" 
-      :toolbar="disabled ? disabledToolbarOptions : toolbarOptions" 
+      :toolbar="toolbarOptions" 
+      ref="wrapperRef"
       theme="snow"
       contentType="html"
       v-bind="$attrs"
@@ -10,7 +11,56 @@
       :content="modelValue"
       :placeholder="disabled ? (modelValue ? '' : '暂未填写') : placeholder"
       @update:content="(val: string) => emit('update:modelValue', val)"
-    />
+    >
+      <template #toolbar>
+        <div :id="toolbarId">
+          <template v-if="!disabled">
+            <button class="ql-bold" title="加粗"></button>
+            <button class="ql-italic" title="斜体"></button>
+            <button class="ql-underline" title="下划线"></button>
+            <button class="ql-strike" title="删除线"></button>
+
+            <button class="ql-divider"></button>
+
+            <button class="ql-blockquote" title="引用"></button>
+            <button class="ql-divider"></button>
+
+            <select class="ql-header">
+              <option v-for="value in headerOptions" :value="value"></option>
+            </select>
+            <button class="ql-divider"></button>
+            
+            <button class="ql-list" value="ordered" title="有序列表"></button>
+            <button class="ql-list" value="bullet" title="无序列表"></button>
+            <button class="ql-list" value="check" title="任务列表"></button>
+            <button class="ql-divider"></button>
+
+            <button class="ql-indent" value="-1" title="减少缩进"></button>
+            <button class="ql-indent" value="+1" title="增加缩进"></button>
+            <button class="ql-divider"></button>
+
+            <button class="ql-direction" value="rtl" title="从右到左"></button>
+            <button class="ql-paste-plain" title="粘贴为无格式普通文本">粘贴为纯文本</button>
+            <button class="ql-divider"></button>
+
+            <select class="ql-size">
+              <option v-for="value in Size.whitelist" :value="value"></option>
+            </select>
+
+            <select class="ql-font">
+              <option v-for="value in Font.whitelist" :value="value"></option>
+            </select>
+            <button class="ql-divider"></button>
+
+            <button class="ql-clean" title="清除格式"></button>
+          </template>
+          <div v-else >
+            <ExclamationCircleOutlined />
+            <span class="ms-2">只读模式,无法编辑</span>
+          </div>
+        </div>
+      </template>
+    </QuillEditor>
   </div>
 </template>
 
@@ -19,7 +69,13 @@ import { QuillEditor, Quill } from '@vueup/vue-quill'
 import '@vueup/vue-quill/dist/vue-quill.snow.css'
 import ImageUploader from 'quill-image-uploader';
 import CommonContent from '@/api/CommonContent';
-import { onMounted } from 'vue';
+import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
+import { onMounted, ref } from 'vue';
+import { RandomUtils } from '@imengyu/imengyu-utils';
+import { message } from 'ant-design-vue';
+
+const toolbarId = 'toolbar' + RandomUtils.genNonDuplicateID(5);
+const wrapperRef = ref();
 
 const Size   = Quill.import('attributors/style/size')
 const Font   = Quill.import('attributors/style/font')
@@ -83,36 +139,30 @@ onMounted(() => {
   }, 100)
 })
 
-const disabledToolbarOptions = {
-  container: [
-    [{ 'text': '只读模式' }]
-  ],
-  
-  // 处理自定义静态文字的渲染
+const toolbarOptions = {
+  container: `#${toolbarId}`,
   handlers: {
-    'text': function(value: any) {
-      // 静态文字不需要交互,这里留空即可
-    }
-  },
-}
-const toolbarOptions = [
-  ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
-  ['blockquote'],
-  ['link', 'image', 'video'],
-
-  [{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
-  [{ 'indent': '-1'}, { 'indent': '+1' }],          // outdent/indent
-  [{ 'direction': 'rtl' }],                         // text direction
-
-  [{ 'size': Size.whitelist }],  // custom dropdown
-  [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
+    'divider': () => {},
+    'text': () => {},
+    'paste-plain': async () => {
+      const quill = wrapperRef.value.getQuill();
+      let text = ''
+      try {
+        text = await navigator.clipboard.readText()
+      } catch (e) {
+        message.error('粘贴失败,请手动粘贴');
+        return;
+      }
 
-  [{ 'color': [] }, { 'background': [] }],          // dropdown with defaults from theme
-  [{ 'font': Font.whitelist }],
-  [{ 'align': [] }],
+      if (!text) return
 
-  ['clean']                                         // remove formatting button
-];
+      const range = quill.getSelection(true)
+      quill.insertText(range.index, text)
+      quill.setSelection(range.index + text.length, 0)
+    },
+  },
+}
+const headerOptions = [ '', 1, 2, 3, 4, 5, 6 ];
 const modules = [
   {
     name: 'imageUploader',
@@ -243,6 +293,23 @@ $fontList: (
     }
 
   }
+  .ql-paste-plain {
+    width: 75px!important;
+    font-size: 12px!important;
+  }
+
+  &.ql-toolbar {
+    .ql-divider {
+      display: inline-block;
+      width: 1px!important;
+      height: 20px!important;
+      margin: 0 5px;
+      margin-top: 3px;
+      padding: 0;
+      border: none;
+      background-color: #efefef;
+    }
+  } 
 }
 
 @each $key, $value in $fontList {

+ 61 - 4
src/pages/admin.vue

@@ -57,6 +57,51 @@
               </template>
             </CommonListBlock>
           </a-tab-pane>
+          <a-tab-pane key="3" tab="传习所">
+            <div class="d-flex justify-content-end">
+              <a-button type="primary" :disabled="true" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
+            </div>
+            <CommonListBlock 
+              :showTotal="true"
+              :rowCount="1"
+              :rowType="5"
+              :dropDownNames="[{
+                options: categoryData.content.value ?? [],
+                label: '分类',
+                defaultSelectedValue: 0,
+              }]"
+              :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadSeminarData(page, pageSize, dropDownValues, searchText)"
+              :showDetail="(item) => router.push({ name: 'FormIch', query: { id: item.id } })"
+              >
+              <template #itemRight="{ item }">
+                <AdminItemState :item="item" />
+                <a-button type="link" @click.stop="router.push({ name: 'FormIch', query: { id: item.id } })">编辑</a-button>
+              </template>
+            </CommonListBlock>
+          </a-tab-pane>
+          <a-tab-pane key="4" tab="重点区域">
+            <div class="d-flex justify-content-end">
+              <a-button type="primary" :disabled="true" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
+            </div>
+            <CommonListBlock 
+              :showTotal="true"
+              :rowCount="1"
+              :rowType="5"
+              :dropDownNames="[{
+                options: categoryData.content.value ?? [],
+                label: '分类',
+                defaultSelectedValue: 0,
+              }]"
+              :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadAreaData(page, pageSize, dropDownValues, searchText)"
+              :showDetail="(item) => router.push({ name: 'FormIch', query: { id: item.id } })"
+              >
+              <template #itemRight="{ item }">
+                <AdminItemState :item="item" />
+                <a-button type="link" @click.stop="router.push({ name: 'FormIch', query: { id: item.id } })">编辑</a-button>
+                <a-button type="link" @click.stop="handleGoWorks(item)">非遗项目作品</a-button>
+              </template>
+            </CommonListBlock>
+          </a-tab-pane>
         </a-tabs>
       </div>
     </section>
@@ -68,14 +113,12 @@ import { h, ref, watch } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { useAuthStore } from '@/stores/auth';
 import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
-//import TestData from '@/assets/data/test.json';
+import { message, Modal } from 'ant-design-vue';
+import type { GetContentListItem } from '@/api/CommonContent';
 import useClipboard from 'vue-clipboard3';
-import EmptyToRecord from '@/components/parts/EmptyToRecord.vue';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
 import CommonListBlock from '@/components/content/CommonListBlock.vue';
-import type { GetContentListItem } from '@/api/CommonContent';
 import InheritorContent from '@/api/inheritor/InheritorContent';
-import { message, Modal } from 'ant-design-vue';
 import AdminItemState from './components/AdminItemState.vue';
 
 const { toClipboard } = useClipboard();
@@ -156,6 +199,20 @@ async function loadIchData(page: number, pageSize: number, dropDownValues: numbe
     }),
   }
 }
+async function loadSeminarData(page: number, pageSize: number, dropDownValues: number[], searchText: string) {
+  return {
+    page,
+    total: 0,
+    data: [],
+  }
+}
+async function loadAreaData(page: number, pageSize: number, dropDownValues: number[], searchText: string) {
+  return {
+    page,
+    total: 0,
+    data: [],
+  }
+}
 
 async function handleCopyAccount(item: GetContentListItem) {
   let result;

+ 2 - 1
src/pages/components/AdminItemState.vue

@@ -2,7 +2,8 @@
   <div class="d-flex flex-row align-items-center gap-3">
 
     <div class="d-flex flex-column">
-      <a-badge v-if="item.progress === 0" status="error" text="待审核" />
+      <a-badge v-if="item.progress === -1" status="error" text="已退回" />
+      <a-badge v-else-if="item.progress === 0" status="processing" text="待审核" />
       <a-badge v-else-if="item.progress === 1" status="processing" text="初审" />
       <a-badge v-else-if="item.progress === 2" status="warning" text="专家审核" />
       <a-badge v-else-if="item.progress === 3" status="success" text="审核通过" />

+ 51 - 18
src/pages/forms/form.vue

@@ -22,20 +22,34 @@
               <div class="d-flex flex-column mt-3">
                 <div class="d-flex flex-row w-100 align-items-center justify-content-between">
                   <span>
-                    <ExclamationCircleOutlined class="me-3" />
-                    提示:上传文件时请勿离开页面防止上传失败,在关闭页面之前请提交您的修改以防丢失。
+                    <ExclamationCircleOutlined class="me-2" />
+                    提示:上传文件时请勿离开页面防止上传失败,离开之前请保存您的修改以防丢失。
                   </span>
                   <a-button size="small" type="primary" @click="showHistory = true">历史版本</a-button>
                 </div>
-                <a-button 
-                  type="primary"
-                  block 
-                  :loading="loading" class="mt-3" 
-                  @click="handleSubmitBase"
-                >
-                  提交
+                <div class="d-flex flex-row w-100 align-items-center justify-content-end mt-3">
+                  <a-popover
+                    title="保存提示"
+                    content="如果未完成编辑,可以先点击保存按钮保存修改,完成后再点击提交审核。您可以在历史版本中查看之前的修改。"
+                  >
+                    <a-button
+                      block 
+                      :loading="loading" class="" 
+                      @click="handleSubmitBase(false)"
+                    >
+                      保存
+                    </a-button>
+                  </a-popover>
+                  <a-button 
+                    type="primary"
+                    block 
+                    :loading="loading" class="ms-3" 
+                    @click="handleSubmitBase(true)"
+                  >
+                    提交
                 </a-button>
               </div>
+            </div>
             </a-tab-pane>
             <a-tab-pane v-if="extendFormOptions" key="2" tab="扩展信息">
               <DynamicForm
@@ -181,23 +195,42 @@ watch(readonly, (newValue) => {
 
 useWindowOnUnLoadConfirm();
 
-async function handleSubmitBase() {
+async function handleSubmitBase(valid: boolean) {
   loading.value = true;
 
+  if (valid) {
+    if (!await new Promise((resolve, reject) => {
+      Modal.confirm({
+        title: '提交提示',
+        content: '是否提交信息审核?填写完整信息后才可提交审核。如果需要离开,可先保存修改下次接着编辑。',
+        okText: '提交',
+        cancelText: '取消',
+        onOk: () => resolve(true),
+        onCancel: () => resolve(false),
+      })
+    })) {
+      loading.value = false;
+      return;
+    }
+  }
+
   const ref = (formBase.value?.getFormRef() as FormInstance);
-  try {
-    await ref.validate();
-  } catch (e) {
-    message.warn('请填写完整信息');
-    loading.value = false;
-    if ((e as any).errorFields)
-      ref.scrollToField((e as any).errorFields[0].name, { block: 'center' })
-    return;
+  if (valid) {
+    try {
+      await ref.validate();
+    } catch (e) {
+      message.warn('请填写完整信息');
+      loading.value = false;
+      if ((e as any).errorFields)
+        ref.scrollToField((e as any).errorFields[0].name, { block: 'center' })
+      return;
+    }
   }
   try {
 
     let result = null;
     const data = await props.save(formModel.value);
+    data.progress = valid ? 0 : -2;
     if (formModel.value instanceof InheritorWorkInfo)
       result = await InheritorContent.saveWorkInfo(data as InheritorWorkInfo);
     else