spring
9 天以前 6f2fbd431fd71a71cfdb5de543959c76f0c47c8f
企业知识库
已添加1个文件
848 ■■■■■ 文件已修改
src/views/collaborativeApproval/knowledgeBase/index.vue 848 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/knowledgeBase/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,848 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">知识标题:</span>
        <el-input
          v-model="searchForm.title"
          style="width: 240px"
          placeholder="请输入知识标题搜索"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
        <span class="search_title ml10">知识类型:</span>
        <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">
          <el-option label="合同特批" :value="'contract'" />
          <el-option label="审批案例" :value="'approval'" />
          <el-option label="解决方案" :value="'solution'" />
          <el-option label="经验总结" :value="'experience'" />
          <el-option label="操作指南" :value="'guide'" />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          æœç´¢
        </el-button>
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增知识</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      ></PIMTable>
    </div>
    <!-- æ–°å¢ž/编辑知识弹窗 -->
    <el-dialog
      v-model="dialogVisible"
      :title="dialogTitle"
      width="800px"
      :close-on-click-modal="false"
    >
      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="知识标题" prop="title">
              <el-input v-model="form.title" placeholder="请输入知识标题" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="知识类型" prop="type">
              <el-select v-model="form.type" placeholder="请选择知识类型" style="width: 100%">
                <el-option label="合同特批" value="contract" />
                <el-option label="审批案例" value="approval" />
                <el-option label="解决方案" value="solution" />
                <el-option label="经验总结" value="experience" />
                <el-option label="操作指南" value="guide" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="适用场景" prop="scenario">
              <el-input v-model="form.scenario" placeholder="请输入适用场景" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="解决效率" prop="efficiency">
              <el-select v-model="form.efficiency" placeholder="请选择解决效率" style="width: 100%">
                <el-option label="显著提升" value="high" />
                <el-option label="一般提升" value="medium" />
                <el-option label="轻微提升" value="low" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="问题描述" prop="problem">
          <el-input
            v-model="form.problem"
            type="textarea"
            :rows="3"
            placeholder="请描述遇到的问题"
          />
        </el-form-item>
        <el-form-item label="解决方案" prop="solution">
          <el-input
            v-model="form.solution"
            type="textarea"
            :rows="4"
            placeholder="请详细描述解决方案"
          />
        </el-form-item>
        <el-form-item label="关键要点" prop="keyPoints">
          <el-input
            v-model="form.keyPoints"
            type="textarea"
            :rows="3"
            placeholder="请输入关键要点,用逗号分隔"
          />
        </el-form-item>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建人" prop="creator">
              <el-input v-model="form.creator" placeholder="请输入创建人" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="使用次数" prop="usageCount">
              <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitForm">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- æŸ¥çœ‹çŸ¥è¯†è¯¦æƒ…弹窗 -->
    <el-dialog
      v-model="viewDialogVisible"
      title="知识详情"
      width="900px"
      :close-on-click-modal="false"
    >
      <div class="knowledge-detail">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="知识标题" :span="2">
            <span class="detail-title">{{ currentKnowledge.title }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="知识类型">
            <el-tag :type="getTypeTagType(currentKnowledge.type)">
              {{ getTypeLabel(currentKnowledge.type) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="适用场景">
            {{ currentKnowledge.scenario }}
          </el-descriptions-item>
          <el-descriptions-item label="解决效率">
            <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)">
              {{ getEfficiencyLabel(currentKnowledge.efficiency) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="使用次数">
            <el-tag type="info">{{ currentKnowledge.usageCount }} æ¬¡</el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="创建人">
            {{ currentKnowledge.creator }}
          </el-descriptions-item>
          <el-descriptions-item label="创建时间">
            {{ currentKnowledge.createTime }}
          </el-descriptions-item>
        </el-descriptions>
        <div class="detail-section">
          <h4>问题描述</h4>
          <div class="detail-content">{{ currentKnowledge.problem }}</div>
        </div>
        <div class="detail-section">
          <h4>解决方案</h4>
          <div class="detail-content">{{ currentKnowledge.solution }}</div>
        </div>
        <div class="detail-section">
          <h4>关键要点</h4>
          <div class="key-points">
            <el-tag
              v-for="(point, index) in currentKnowledge.keyPoints.split(',')"
              :key="index"
              type="success"
              style="margin-right: 8px; margin-bottom: 8px;"
            >
              {{ point.trim() }}
            </el-tag>
          </div>
        </div>
        <div class="detail-section">
          <h4>使用统计</h4>
          <div class="usage-stats">
            <el-row :gutter="20">
              <el-col :span="8">
                <div class="stat-item">
                  <div class="stat-number">{{ currentKnowledge.usageCount }}</div>
                  <div class="stat-label">使用次数</div>
                </div>
              </el-col>
              <el-col :span="8">
                <div class="stat-item">
                  <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div>
                  <div class="stat-label">效率提升</div>
                </div>
              </el-col>
              <el-col :span="8">
                <div class="stat-item">
                  <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div>
                  <div class="stat-label">平均节省时间</div>
                </div>
              </el-col>
            </el-row>
          </div>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="viewDialogVisible = false">关闭</el-button>
          <el-button type="primary" @click="copyKnowledge">复制知识</el-button>
          <el-button type="success" @click="markAsFavorite">收藏</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, toRefs } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
// è¡¨å•验证规则
const rules = {
  title: [
    { required: true, message: "请输入知识标题", trigger: "blur" }
  ],
  type: [
    { required: true, message: "请选择知识类型", trigger: "change" }
  ],
  problem: [
    { required: true, message: "请描述遇到的问题", trigger: "blur" }
  ],
  solution: [
    { required: true, message: "请详细描述解决方案", trigger: "blur" }
  ]
};
// å“åº”式数据
const data = reactive({
  searchForm: {
    title: "",
    type: "",
  },
  tableLoading: false,
  page: {
    current: 1,
    size: 100,
    total: 0,
  },
  tableData: [],
  selectedIds: [],
  form: {
    title: "",
    type: "",
    scenario: "",
    efficiency: "medium",
    problem: "",
    solution: "",
    keyPoints: "",
    creator: "",
    usageCount: 0
  },
  dialogVisible: false,
  dialogTitle: "",
  dialogType: "add",
  viewDialogVisible: false,
  currentKnowledge: {}
});
const {
  searchForm,
  tableLoading,
  page,
  tableData,
  selectedIds,
  form,
  dialogVisible,
  dialogTitle,
  dialogType,
  viewDialogVisible,
  currentKnowledge
} = toRefs(data);
// è¡¨å•引用
const formRef = ref();
// è¡¨æ ¼åˆ—配置
const tableColumn = ref([
  {
    label: "知识标题",
    prop: "title",
    showOverflowTooltip: true,
  },
  {
    label: "知识类型",
    prop: "type",
    dataType: "tag",
    formatData: (params) => {
      const typeMap = {
        contract: "合同特批",
        approval: "审批案例",
        solution: "解决方案",
        experience: "经验总结",
        guide: "操作指南"
      };
      return typeMap[params] || params;
    },
    formatType: (params) => {
      const typeMap = {
        contract: "success",
        approval: "warning",
        solution: "primary",
        experience: "info",
        guide: "danger"
      };
      return typeMap[params] || "info";
    }
  },
  {
    label: "适用场景",
    prop: "scenario",
    width: 150,
    showOverflowTooltip: true,
  },
  {
    label: "解决效率",
    prop: "efficiency",
    dataType: "tag",
    formatData: (params) => {
      const efficiencyMap = {
        high: "显著提升",
        medium: "一般提升",
        low: "轻微提升"
      };
      return efficiencyMap[params] || params;
    },
    formatType: (params) => {
      const typeMap = {
        high: "success",
        medium: "warning",
        low: "info"
      };
      return typeMap[params] || "info";
    }
  },
  {
    label: "使用次数",
    prop: "usageCount",
    width: 100,
    align: "center"
  },
  {
    label: "创建人",
    prop: "creator",
    width: 120,
  },
  {
    label: "创建时间",
    prop: "createTime",
    width: 180,
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 200,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        }
      },
      {
        name: "查看",
        type: "text",
        clickFun: (row) => {
          viewKnowledge(row);
        }
      }
    ]
  }
]);
// æ¨¡æ‹Ÿæ•°æ®
let mockData = [
  {
    id: "1",
    title: "特殊合同审批流程优化方案",
    type: "contract",
    scenario: "大额合同快速审批",
    efficiency: "high",
    problem: "大额合同审批流程复杂,审批时间长,影响业务进展",
    solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天",
    keyPoints: "绿色通道条件,简化流程,审批权限,时间控制",
    creator: "张经理",
    usageCount: 15,
    createTime: "2024-01-15 10:30:00"
  },
  {
    id: "2",
    title: "跨部门协作审批经验总结",
    type: "experience",
    scenario: "多部门协作项目",
    efficiency: "medium",
    problem: "跨部门项目审批时,各部门意见不统一,审批进度缓慢",
    solution: "建立项目协调机制,指定项目负责人,定期召开协调会议,统一各方意见后再进行审批",
    keyPoints: "项目协调,定期会议,统一意见,负责人制度",
    creator: "李主管",
    usageCount: 8,
    createTime: "2024-01-14 15:20:00"
  },
  {
    id: "3",
    title: "紧急采购审批操作指南",
    type: "guide",
    scenario: "紧急采购需求",
    efficiency: "high",
    problem: "紧急采购时审批流程复杂,无法满足紧急需求",
    solution: "制定紧急采购审批标准,明确紧急程度分级,不同级别采用不同审批流程,确保紧急需求得到及时处理",
    keyPoints: "紧急分级,标准制定,流程简化,及时处理",
    creator: "王专员",
    usageCount: 12,
    createTime: "2024-01-13 09:15:00"
  }
];
// çŸ¥è¯†æ ‡é¢˜æ¨¡æ¿
const titleTemplates = [
  "{type}审批流程优化方案",
  "{scenario}处理经验总结",
  "{type}特殊情况处理指南",
  "{scenario}快速审批方案",
  "{type}标准化操作流程",
  "{scenario}问题解决方案",
  "{type}最佳实践总结",
  "{scenario}效率提升方案"
];
// çŸ¥è¯†ç±»åž‹é…ç½®
const knowledgeTypes = [
  { type: "contract", label: "合同特批", efficiency: "high" },
  { type: "approval", label: "审批案例", efficiency: "medium" },
  { type: "solution", label: "解决方案", efficiency: "high" },
  { type: "experience", label: "经验总结", efficiency: "medium" },
  { type: "guide", label: "操作指南", efficiency: "low" }
];
// åœºæ™¯åˆ—表
const scenarios = ["大额合同审批", "跨部门协作", "紧急采购", "特殊申请", "流程优化", "问题处理", "标准化建设", "效率提升"];
// è‡ªåŠ¨ç”Ÿæˆæ–°æ•°æ®
const generateNewData = () => {
  const newId = (mockData.length + 1).toString();
  const now = new Date();
  const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)];
  const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)];
  // ç”Ÿæˆéšæœºæ ‡é¢˜
  let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)];
  title = title
    .replace('{type}', randomType.label)
    .replace('{scenario}', randomScenario);
  const newKnowledge = {
    id: newId,
    title: title,
    type: randomType.type,
    scenario: randomScenario,
    efficiency: randomType.efficiency,
    problem: `在${randomScenario}过程中遇到的问题描述...`,
    solution: `针对${randomScenario}的解决方案和操作步骤...`,
    keyPoints: "关键要点1,关键要点2,关键要点3,关键要点4",
    creator: ["张经理", "李主管", "王专员", "刘总监"][Math.floor(Math.random() * 4)],
    usageCount: Math.floor(Math.random() * 20) + 1,
    createTime: now.toLocaleString()
  };
  // æ·»åŠ åˆ°æ•°æ®å¼€å¤´
  mockData.unshift(newKnowledge);
  // ä¿æŒæ•°æ®é‡åœ¨åˆç†èŒƒå›´å†…(最多保留30条)
  if (mockData.length > 30) {
    mockData = mockData.slice(0, 30);
  }
  console.log(`[${new Date().toLocaleString()}] è‡ªåŠ¨ç”Ÿæˆæ–°çŸ¥è¯†: ${title}`);
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  getList();
  startAutoRefresh();
});
// å¼€å§‹è‡ªåŠ¨åˆ·æ–°
const startAutoRefresh = () => {
  setInterval(() => {
    generateNewData();
    getList();
  }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms)
};
// æŸ¥è¯¢æ•°æ®
const handleQuery = () => {
  page.value.current = 1;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  setTimeout(() => {
    let filteredData = [...mockData];
    if (searchForm.value.title) {
      filteredData = filteredData.filter(item =>
        item.title.toLowerCase().includes(searchForm.value.title.toLowerCase())
      );
    }
    if (searchForm.value.type) {
      filteredData = filteredData.filter(item => item.type === searchForm.value.type);
    }
    tableData.value = filteredData;
    page.value.total = filteredData.length;
    tableLoading.value = false;
  }, 500);
};
// åˆ†é¡µå¤„理
const pagination = (obj) => {
  page.value.current = obj.page;
  page.value.size = obj.limit;
  handleQuery();
};
// é€‰æ‹©å˜åŒ–处理
const handleSelectionChange = (selection) => {
  selectedIds.value = selection.map(item => item.id);
};
// æ‰“开表单
const openForm = (type, row = null) => {
  dialogType.value = type;
  if (type === "add") {
    dialogTitle.value = "新增知识";
    // é‡ç½®è¡¨å•
    Object.assign(form.value, {
      title: "",
      type: "",
      scenario: "",
      efficiency: "medium",
      problem: "",
      solution: "",
      keyPoints: "",
      creator: "",
      usageCount: 0
    });
  } else if (type === "edit" && row) {
    dialogTitle.value = "编辑知识";
    Object.assign(form.value, {
      title: row.title,
      type: row.type,
      scenario: row.scenario,
      efficiency: row.efficiency,
      problem: row.problem,
      solution: row.solution,
      keyPoints: row.keyPoints,
      creator: row.creator,
      usageCount: row.usageCount
    });
  }
  dialogVisible.value = true;
};
// æŸ¥çœ‹çŸ¥è¯†è¯¦æƒ…
const viewKnowledge = (row) => {
  currentKnowledge.value = { ...row };
  viewDialogVisible.value = true;
};
// èŽ·å–ç±»åž‹æ ‡ç­¾ç±»åž‹
const getTypeTagType = (type) => {
  const typeMap = {
    contract: "success",
    approval: "warning",
    solution: "primary",
    experience: "info",
    guide: "danger"
  };
  return typeMap[type] || "info";
};
// èŽ·å–ç±»åž‹æ ‡ç­¾æ–‡æœ¬
const getTypeLabel = (type) => {
  const typeMap = {
    contract: "合同特批",
    approval: "审批案例",
    solution: "解决方案",
    experience: "经验总结",
    guide: "操作指南"
  };
  return typeMap[type] || type;
};
// èŽ·å–æ•ˆçŽ‡æ ‡ç­¾ç±»åž‹
const getEfficiencyTagType = (efficiency) => {
  const typeMap = {
    high: "success",
    medium: "warning",
    low: "info"
  };
  return typeMap[efficiency] || "info";
};
// èŽ·å–æ•ˆçŽ‡æ ‡ç­¾æ–‡æœ¬
const getEfficiencyLabel = (efficiency) => {
  const efficiencyMap = {
    high: "显著提升",
    medium: "一般提升",
    low: "轻微提升"
  };
  return efficiencyMap[efficiency] || efficiency;
};
// èŽ·å–æ•ˆçŽ‡æå‡ç™¾åˆ†æ¯”
const getEfficiencyScore = (efficiency) => {
  const scoreMap = {
    high: 40,
    medium: 25,
    low: 15
  };
  return scoreMap[efficiency] || 0;
};
// èŽ·å–å¹³å‡èŠ‚çœæ—¶é—´
const getTimeSaved = (efficiency) => {
  const timeMap = {
    high: "2-3天",
    medium: "1-2天",
    low: "0.5-1天"
  };
  return timeMap[efficiency] || "未知";
};
// å¤åˆ¶çŸ¥è¯†
const copyKnowledge = () => {
  const knowledgeText = `
知识标题:${currentKnowledge.value.title}
知识类型:${getTypeLabel(currentKnowledge.value.type)}
适用场景:${currentKnowledge.value.scenario}
问题描述:${currentKnowledge.value.problem}
解决方案:${currentKnowledge.value.solution}
关键要点:${currentKnowledge.value.keyPoints}
创建人:${currentKnowledge.value.creator}
  `.trim();
  // å¤åˆ¶åˆ°å‰ªè´´æ¿
  navigator.clipboard.writeText(knowledgeText).then(() => {
    ElMessage.success("知识内容已复制到剪贴板");
  }).catch(() => {
    ElMessage.error("复制失败,请手动复制");
  });
};
// æ”¶è—çŸ¥è¯†
const markAsFavorite = () => {
  // å¢žåŠ ä½¿ç”¨æ¬¡æ•°
  const index = mockData.findIndex(item => item.id === currentKnowledge.value.id);
  if (index !== -1) {
    mockData[index].usageCount += 1;
    currentKnowledge.value.usageCount += 1;
  }
  ElMessage.success("已收藏,使用次数+1");
};
// æäº¤çŸ¥è¯†è¡¨å•
const submitForm = async () => {
  try {
    await formRef.value.validate();
    if (dialogType.value === "add") {
      // æ–°å¢žçŸ¥è¯†
      const newKnowledge = {
        id: (mockData.length + 1).toString(),
        title: form.value.title,
        type: form.value.type,
        scenario: form.value.scenario,
        efficiency: form.value.efficiency,
        problem: form.value.problem,
        solution: form.value.solution,
        keyPoints: form.value.keyPoints,
        creator: form.value.creator,
        usageCount: form.value.usageCount,
        createTime: new Date().toLocaleString()
      };
      mockData.unshift(newKnowledge);
      ElMessage.success("知识创建成功");
    } else {
      // ç¼–辑知识
      const index = mockData.findIndex(item => item.id === selectedIds.value[0]);
      if (index !== -1) {
        Object.assign(mockData[index], {
          title: form.value.title,
          type: form.value.type,
          scenario: form.value.scenario,
          efficiency: form.value.efficiency,
          problem: form.value.problem,
          solution: form.value.solution,
          keyPoints: form.value.keyPoints,
          creator: form.value.creator,
          usageCount: form.value.usageCount
        });
        ElMessage.success("知识更新成功");
      }
    }
    dialogVisible.value = false;
    getList();
  } catch (error) {
    console.error("表单验证失败:", error);
  }
};
// åˆ é™¤çŸ¥è¯†
const handleDelete = () => {
  if (selectedIds.value.length === 0) {
    ElMessage.warning("请选择要删除的知识");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    // ä»ŽmockData中删除选中的项
    selectedIds.value.forEach(id => {
      const index = mockData.findIndex(item => item.id === id);
      if (index !== -1) {
        mockData.splice(index, 1);
      }
    });
    ElMessage.success("删除成功");
    selectedIds.value = [];
    getList();
  }).catch(() => {
    // ç”¨æˆ·å–消
  });
};
</script>
<style scoped>
.auto-refresh-info {
  margin-bottom: 15px;
}
.auto-refresh-info .el-alert {
  border-radius: 8px;
}
.dialog-footer {
  text-align: right;
}
.knowledge-detail {
  padding: 20px 0;
}
.detail-title {
  font-size: 18px;
  font-weight: bold;
  color: #303133;
}
.detail-section {
  margin-top: 24px;
}
.detail-section h4 {
  margin: 0 0 12px 0;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
  border-left: 4px solid #409eff;
  padding-left: 12px;
}
.detail-content {
  background: #f8f9fa;
  padding: 16px;
  border-radius: 6px;
  line-height: 1.6;
  color: #606266;
  white-space: pre-wrap;
}
.key-points {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.usage-stats {
  margin-top: 16px;
}
.stat-item {
  text-align: center;
  padding: 20px;
  background: #f8f9fa;
  border-radius: 8px;
}
.stat-number {
  font-size: 24px;
  font-weight: bold;
  color: #409eff;
  margin-bottom: 8px;
}
.stat-label {
  font-size: 14px;
  color: #909399;
}
</style>