张诺
3 天以前 8ba57678bb7b37293fb966a82ab1a73853941e9b
feat: 将知识库模块从会议管理迁移至协同办公

- 将知识库相关页面从 managementMeetings 目录移动到 cooperativeOffice/collaborativeApproval 目录
- 更新页面路由配置,确保导航路径正确
- 重构知识库详情页面,增加文件上传功能并优化表单结构
- 修改首页和工作台中的知识库跳转链接指向新路径
已添加1个文件
已重命名1个文件
已修改3个文件
已删除1个文件
1125 ■■■■ 文件已修改
src/pages.json 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/detail.vue 515 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/index.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/indexItem.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/knowledgeBase/detail.vue 500 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json
@@ -458,9 +458,16 @@
      }
    },
    {
      "path": "pages/managementMeetings/knowledgeBase/index",
      "path": "pages/cooperativeOffice/collaborativeApproval/knowledgeBase/index",
      "style": {
        "navigationBarTitleText": "知识库",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/knowledgeBase/detail",
      "style": {
        "navigationBarTitleText": "知识库详情",
        "navigationStyle": "custom"
      }
    },
@@ -496,13 +503,6 @@
      "path": "pages/managementMeetings/rulesRegulationsManagement/fileList",
      "style": {
        "navigationBarTitleText": "规章制度文件管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/knowledgeBase/detail",
      "style": {
        "navigationBarTitleText": "知识库详情",
        "navigationStyle": "custom"
      }
    },
src/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,515 @@
<template>
  <view class="client-visit-detail">
    <PageHeader :title="detailType === 1 ? '新增知识' : (detailType === 2 ? '编辑知识' : '知识详情')"
                @back="goBack" />
    <view class="form-container">
      <u-form ref="formRef"
              :model="form"
              label-width="90">
        <u-cell-group title="基本信息">
          <u-form-item label="知识标题"
                       prop="title"
                       required
                       border-bottom>
            <u-input v-model="form.title"
                     :readonly="readonly"
                     placeholder="请输入知识标题" />
          </u-form-item>
          <u-form-item label="知识类型"
                       prop="type"
                       required
                       border-bottom>
            <u-input v-model="typeName"
                     readonly
                     placeholder="请选择知识类型"
                     @click="!readonly && (showTypeSheet = true)" />
            <template v-if="!readonly"
                      #right>
              <up-icon name="arrow-right"
                       @click="showTypeSheet = true"></up-icon>
            </template>
          </u-form-item>
          <u-form-item label="适用场景"
                       prop="scenario"
                       border-bottom>
            <u-input v-model="form.scenario"
                     :readonly="readonly"
                     placeholder="请输入适用场景" />
          </u-form-item>
          <u-form-item label="解决效率"
                       prop="efficiency"
                       border-bottom>
            <u-input v-model="efficiencyName"
                     readonly
                     placeholder="请选择解决效率"
                     @click="!readonly && (showEfficiencySheet = true)" />
            <template v-if="!readonly"
                      #right>
              <up-icon name="arrow-right"
                       @click="showEfficiencySheet = true"></up-icon>
            </template>
          </u-form-item>
        </u-cell-group>
        <u-cell-group title="详细内容">
          <u-form-item label="问题描述"
                       required
                       prop="problem"
                       border-bottom>
            <u-textarea v-model="form.problem"
                        rows="4"
                        :disabled="readonly"
                        placeholder="请输入问题描述" />
          </u-form-item>
          <u-form-item label="解决方案"
                       prop="solution"
                       required
                       border-bottom>
            <u-textarea v-model="form.solution"
                        rows="4"
                        :disabled="readonly"
                        placeholder="请输入解决方案" />
          </u-form-item>
          <u-form-item label="关键要点"
                       prop="keyPoints"
                       border-bottom>
            <u-textarea v-model="form.keyPoints"
                        rows="4"
                        :disabled="readonly"
                        placeholder="请输入关键要点,用逗号分隔" />
          </u-form-item>
        </u-cell-group>
        <u-cell-group title="其他信息">
          <u-form-item label="创建人"
                       prop="creator"
                       border-bottom>
            <u-input v-model="form.creator"
                     readonly
                     placeholder="请选择创建人"
                     @click="!readonly && (showCreatorSheet = true)" />
            <template v-if="!readonly"
                      #right>
              <up-icon name="arrow-right"
                       @click="showCreatorSheet = true"></up-icon>
            </template>
          </u-form-item>
          <u-form-item label="使用次数"
                       prop="usageCount"
                       border-bottom>
            <uni-number-box v-model="form.usageCount"
                            :min="0"
                            :disabled="readonly" />
          </u-form-item>
        </u-cell-group>
        <u-cell-group title="附件材料">
          <view class="upload-section">
            <view class="upload-tip" v-if="!readonly">
              æ”¯æŒæ–‡æ¡£ï¼ˆdoc, docx, xls, xlsx, pdf, txt)和图片(jpg, jpeg, png, gif)格式
            </view>
            <view class="file-list" v-if="form.commonFileList && form.commonFileList.length > 0">
              <view v-for="(file, index) in form.commonFileList" :key="index" class="file-item">
                <up-icon name="file-text" size="20" color="#667eea"></up-icon>
                <text class="file-name" @click="previewFile(file)">{{ file.name || file.fileName }}</text>
                <up-icon v-if="!readonly" name="close-circle-fill" size="18" color="#ff4d4f" @click="handleRemoveFile(index)"></up-icon>
              </view>
            </view>
            <view v-if="!readonly" class="upload-btn-container">
               <up-upload :fileList="fileList"
                         @afterRead="afterRead"
                         @delete="deleteFile"
                         multiple
                         :maxCount="10"
                         :previewImage="true">
                <view class="custom-upload-btn">
                  <up-icon name="plus" size="24" color="#999"></up-icon>
                  <text>添加附件</text>
                </view>
              </up-upload>
            </view>
          </view>
        </u-cell-group>
      </u-form>
      <!-- æäº¤æŒ‰é’® -->
      <view v-if="!readonly"
            class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSubmit"
                  :loading="loading">保存</u-button>
      </view>
    </view>
    <!-- é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showTypeSheet"
                     :actions="typeOptions"
                     title="请选择知识类型"
                     @select="onTypeSelect"
                     @close="showTypeSheet = false" />
    <up-action-sheet :show="showEfficiencySheet"
                     :actions="efficiencyOptions"
                     title="请选择解决效率"
                     @select="onEfficiencySelect"
                     @close="showEfficiencySheet = false" />
    <up-action-sheet :show="showCreatorSheet"
                     :actions="creatorOptions"
                     title="请选择创建人"
                     @select="onCreatorSelect"
                     @close="showCreatorSheet = false" />
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import { useDict } from "@/utils/dict";
  import {
    addKnowledgeBase,
    updateKnowledgeBase,
  } from "@/api/managementMeetings/knowledgeBase";
  import { userListNoPageByTenantId } from "@/api/system/user";
  import upload from "@/utils/upload";
  defineOptions({ name: "knowledge-base-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const userStore = useUserStore();
  // é¡µé¢çŠ¶æ€
  const detailType = ref(1); // 1-新增, 2-编辑, 3-详情
  const knowledgeId = ref("");
  const readonly = ref(false);
  const loading = ref(false);
  const formRef = ref(null);
  // è¡¨å•数据
  const form = ref({
    title: "",
    type: "",
    scenario: "",
    efficiency: "",
    problem: "",
    solution: "",
    keyPoints: "",
    creator: userStore.nickName || "",
    usageCount: 0,
    tempFileIds: [],
    commonFileList: []
  });
  // é€‰æ‹©å™¨çŠ¶æ€
  const showTypeSheet = ref(false);
  const showEfficiencySheet = ref(false);
  const showCreatorSheet = ref(false);
  // æ•°æ®å­—å…¸
  const { knowledge_type } = useDict("knowledge_type");
  const typeOptions = computed(() => {
    return (knowledge_type?.value || []).map(item => ({
      name: item.label,
      value: item.value
    }));
  });
  const typeName = computed(() => {
    const item = typeOptions.value.find(i => String(i.value) === String(form.value.type));
    return item ? item.name : (form.value.type || "");
  });
  const efficiencyOptions = [
    { name: "显著提升", value: "high" },
    { name: "一般提升", value: "medium" },
    { name: "轻微提升", value: "low" }
  ];
  const efficiencyName = computed(() => {
    const item = efficiencyOptions.find(i => i.value === form.value.efficiency);
    return item ? item.name : (form.value.efficiency || "");
  });
  const creatorOptions = ref([]);
  // æ–‡ä»¶ä¸Šä¼ ç›¸å…³
  const fileList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // é€‰æ‹©å¤„理
  const onTypeSelect = (action) => {
    form.value.type = action.value;
    showTypeSheet.value = false;
  };
  const onEfficiencySelect = (action) => {
    form.value.efficiency = action.value;
    showEfficiencySheet.value = false;
  };
  const onCreatorSelect = (action) => {
    form.value.creator = action.name;
    showCreatorSheet.value = false;
  };
  // èŽ·å–åˆ›å»ºäººåˆ—è¡¨
  const getCreatorOptions = async () => {
    try {
      const res = await userListNoPageByTenantId();
      if (res.code === 200) {
        creatorOptions.value = (res.data || []).map(item => ({
          name: item.nickName,
          value: item.userId
        }));
      }
    } catch (e) {
      console.error("获取创建人列表失败:", e);
    }
  };
  // æ–‡ä»¶å¤„理
  const afterRead = async (event) => {
    const { file } = event;
    const lists = [].concat(file);
    for (let i = 0; i < lists.length; i++) {
      const item = lists[i];
      try {
        uni.showLoading({ title: '上传中...' });
        const res = await upload({
          url: '/file/upload',
          filePath: item.url,
          name: 'file'
        });
        uni.hideLoading();
        if (res.code === 200) {
          if (!form.value.tempFileIds) form.value.tempFileIds = [];
          form.value.tempFileIds.push(res.data.tempId);
          if (!form.value.commonFileList) form.value.commonFileList = [];
          form.value.commonFileList.push({
            id: res.data.tempId,
            name: item.name || '未命名文件',
            url: res.data.url
          });
          showToast('上传成功');
        } else {
          showToast(res.msg || '上传失败');
        }
      } catch (e) {
        uni.hideLoading();
        console.error('上传失败:', e);
        showToast('上传失败');
      }
    }
  };
  const deleteFile = (event) => {
    const { index } = event;
    form.value.commonFileList.splice(index, 1);
    if (form.value.tempFileIds) {
      form.value.tempFileIds.splice(index, 1);
    }
  };
  const handleRemoveFile = (index) => {
    form.value.commonFileList.splice(index, 1);
    if (form.value.tempFileIds) {
      form.value.tempFileIds.splice(index, 1);
    }
  };
  const previewFile = (file) => {
    if (file.url) {
      // å¦‚果是图片,预览图片
      const isImage = /\.(jpg|jpeg|png|gif)$/i.test(file.name || file.fileName || file.url);
      if (isImage) {
        uni.previewImage({
          urls: [file.url]
        });
      } else {
        // å…¶ä»–文件尝试打开
        uni.downloadFile({
          url: file.url,
          success: (res) => {
            if (res.statusCode === 200) {
              uni.openDocument({
                filePath: res.tempFilePath,
                success: () => console.log('打开文档成功')
              });
            }
          }
        });
      }
    }
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    if (!form.value.title) return showToast("请输入知识标题");
    if (!form.value.type) return showToast("请选择知识类型");
    if (!form.value.problem) return showToast("请输入问题描述");
    if (!form.value.solution) return showToast("请输入解决方案");
    try {
      loading.value = true;
      const apiCall = detailType.value === 1 ? addKnowledgeBase : updateKnowledgeBase;
      const res = await apiCall(form.value);
      loading.value = false;
      if (res.code === 200) {
        showToast("保存成功");
        setTimeout(() => goBack(), 500);
      } else {
        showToast(res.msg || "保存失败");
      }
    } catch (e) {
      loading.value = false;
      console.error("保存失败:", e);
      showToast("系统异常,保存失败");
    }
  };
  onLoad(options => {
    detailType.value = Number(options.detailType || 1);
    knowledgeId.value = options.id || "";
    readonly.value = detailType.value === 3;
    if (detailType.value !== 1) {
      try {
        const cached = uni.getStorageSync("knowledgeBase");
        if (cached) {
          // ç¡®ä¿æ•°æ®æ˜¯å¯¹è±¡ç±»åž‹
          const data = typeof cached === 'string' ? JSON.parse(cached) : cached;
          // å¦‚果传入了 id,则校验 id æ˜¯å¦ä¸€è‡´
          if (!knowledgeId.value || String(data.id) === String(knowledgeId.value)) {
            form.value = JSON.parse(JSON.stringify(data));
            // å…¼å®¹å¤„理文件列表
            if (form.value.commonFileList) {
              form.value.tempFileIds = form.value.commonFileList.map(f => f.id || f.tempId);
            }
          }
        }
      } catch (e) {
        console.error("解析缓存数据失败:", e);
      }
    }
  });
  onMounted(() => {
    getCreatorOptions();
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .client-visit-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 80px;
  }
  .form-container {
    padding: 10px;
    background: #fff;
  }
  .upload-section {
    padding: 15px 0;
    .section-title {
      font-size: 14px;
      color: #333;
      font-weight: bold;
      margin-bottom: 10px;
    }
    .upload-tip {
      font-size: 12px;
      color: #999;
      margin-bottom: 10px;
    }
    .file-list {
      margin-bottom: 15px;
      .file-item {
        display: flex;
        align-items: center;
        background: #f5f7fa;
        padding: 8px 12px;
        border-radius: 4px;
        margin-bottom: 8px;
        .file-name {
          flex: 1;
          margin: 0 10px;
          font-size: 14px;
          color: #667eea;
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }
      }
    }
  }
  .custom-upload-btn {
    width: 80px;
    height: 80px;
    border: 1px dashed #d9d9d9;
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background: #fafafa;
    text {
      font-size: 12px;
      color: #999;
      margin-top: 5px;
    }
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 15px 0;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
    z-index: 100;
    .u-button {
      width: 45%;
      border-radius: 25px;
    }
    .sign-btn {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      border: none;
    }
  }
</style>
src/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/index.vue
ÎļþÃû´Ó src/pages/managementMeetings/knowledgeBase/index.vue ÐÞ¸Ä
@@ -10,11 +10,11 @@
          <up-input class="search-text"
                    placeholder="请输入知识标题"
                    v-model="name"
                    @blur="getList"
                    @blur="handleQuery"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
              @click="handleQuery">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
@@ -70,21 +70,23 @@
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      plain
                      @click="viewDetail(item,3)">
              æŸ¥çœ‹
            </u-button>
            <!-- <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="confirmDelete(item)">
              åˆ é™¤
              æŸ¥çœ‹è¯¦æƒ…
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,2)">
              ç¼–辑
            </u-button> -->
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      plain
                      @click="confirmDelete(item)">
              åˆ é™¤
            </u-button>
          </view>
        </view>
      </view>
@@ -94,12 +96,12 @@
      <text>暂无知识记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <!-- <view class="fab-button"
    <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view> -->
    </view>
  </view>
</template>
@@ -113,16 +115,15 @@
    delKnowledgeBase,
  } from "@/api/managementMeetings/knowledgeBase";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-index" });
  defineOptions({ name: "knowledge-base-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
  const userStore = useUserStore();
@@ -136,11 +137,14 @@
  const goBack = () => {
    uni.navigateBack();
  };
  const { knowledge_type } = useDict("knowledge_type");
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    return getKnowledgeTypeLabel(params);
  };
  const formatReceiptType1 = params => {
    if (params == "high") {
      return "显著提升";
@@ -152,6 +156,7 @@
      return "未知";
    }
  };
  const getTagClass = type => {
    if (type == "high") {
      return "success";
@@ -163,10 +168,11 @@
      return "info";
    }
  };
  const knowledgeTypeOptions = computed(() => knowledge_type?.value || []);
  // èŽ·å–çŸ¥è¯†ç±»åž‹æ ‡ç­¾
  const getKnowledgeTypeLabel = val => {
    console.log(knowledgeTypeOptions, "knowledgeTypeOptions");
    const item = knowledgeTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
@@ -174,6 +180,10 @@
  };
  // æŸ¥è¯¢åˆ—表
  const handleQuery = () => {
    getList();
  };
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
@@ -208,16 +218,16 @@
  // æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const addVisit = () => {
    uni.navigateTo({
      url: "/pages/managementMeetings/knowledgeBase/detail?detailType=1",
      url: "/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/detail?detailType=1",
    });
  };
  // ç¼–辑
  // æŸ¥çœ‹/编辑
  const viewDetail = (item, detailType) => {
    uni.setStorageSync("knowledgeBase", item);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/knowledgeBase/detail?detailType=" +
        "/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/detail?detailType=" +
        detailType +
        "&id=" +
        item.id,
@@ -266,7 +276,7 @@
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  @import "../../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
@@ -292,19 +302,37 @@
    color: #333; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬é¢œè‰²
  }
  // çŠ¶æ€æ ·å¼
  .status-enabled {
    color: #28a745; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æˆåŠŸé¢œè‰²
  }
  .status-disabled {
    color: #dc3545; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„错误颜色
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
    position: fixed;
    bottom: 40px;
    right: 20px;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 100;
  }
  .action-buttons {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 10px;
    padding-bottom: 10px;
  }
  .action-btn {
    margin: 0 !important;
  }
  .no-data {
    padding: 50px 0;
    text-align: center;
    color: #999;
  }
</style>
src/pages/indexItem.vue
@@ -67,7 +67,7 @@
    "会议总结": "/pages/managementMeetings/meetSummary/index",
    "会议看板": "/pages/managementMeetings/meetingBoard/index",
    "通知公告": "/pages/cooperativeOffice/noticeManagement/index",
    "知识库": "/pages/managementMeetings/knowledgeBase/index",
    "知识库": "/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/index",
    "用印管理": "/pages/managementMeetings/sealManagement/index",
    "规章制度": "/pages/managementMeetings/rulesRegulationsManagement/index",
    "客户拜访": "/pages/cooperativeOffice/clientVisit/index",
src/pages/managementMeetings/knowledgeBase/detail.vue
ÎļþÒÑɾ³ý
src/pages/works.vue
@@ -673,7 +673,7 @@
        break;
      case "知识库":
        uni.navigateTo({
          url: "/pages/managementMeetings/knowledgeBase/index",
          url: "/pages/cooperativeOffice/collaborativeApproval/knowledgeBase/index",
        });
        break;
      case "用印管理":