edit.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <template>
  2. <div class="case-edit-container layout-pd">
  3. <el-card shadow="never" style="padding: 0 50px">
  4. <el-form :model="state.ruleForm" ref="ruleFormRef" label-width="120px" scroll-to-error>
  5. <el-row :gutter="35">
  6. <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="8">
  7. <el-skeleton :loading="state.loading" animated>
  8. <template #template>
  9. <el-form-item label="案例分类">
  10. <el-skeleton-item variant="h1" />
  11. </el-form-item>
  12. </template>
  13. <template #default>
  14. <el-form-item label="案例分类" prop="caseTypeId" :rules="[{ required: true, message: '请选择案例分类', trigger: 'change' }]">
  15. <VTreeDrop
  16. :data="state.typeData"
  17. checkable
  18. keyField="id"
  19. titleField="name"
  20. v-model="state.ruleForm.caseTypeId"
  21. @checked-change="selectType"
  22. :dropHeight="400"
  23. dropPlaceholder="案例分类"
  24. dropdownWidthFixed
  25. clearable
  26. searchPlaceholder="案例分类名称"
  27. checkedButtonText="查看已选"
  28. :show-footer="false"
  29. :cascade="false"
  30. >
  31. </VTreeDrop>
  32. </el-form-item>
  33. </template>
  34. </el-skeleton>
  35. </el-col>
  36. <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="8">
  37. <el-skeleton :loading="state.loading" animated>
  38. <template #template>
  39. <el-form-item>
  40. <template #label>
  41. <div style="height: 34px; display: flex; align-items: center">
  42. 失效时间
  43. <el-tooltip placement="top-start">
  44. <SvgIcon name="ele-QuestionFilled" size="18px" class="ml3" />
  45. <template #content> 不设置则代表永久有效;到达预设失效时间,案例将自动下架 </template>
  46. </el-tooltip>
  47. </div>
  48. </template>
  49. <el-skeleton-item variant="h1" />
  50. </el-form-item>
  51. </template>
  52. <template #default>
  53. <el-form-item label="失效时间" prop="expiredTime" :rules="[{ required: false, message: '请选择失效时间', trigger: 'change' }]">
  54. <template #label>
  55. <div style="height: 34px; display: flex; align-items: center">
  56. 失效时间
  57. <el-tooltip placement="top-start">
  58. <SvgIcon name="ele-QuestionFilled" size="18px" class="ml3" />
  59. <template #content> 不设置则代表永久有效;到达预设失效时间,案例将自动下架 </template>
  60. </el-tooltip>
  61. </div>
  62. </template>
  63. <el-date-picker
  64. v-model="state.ruleForm.expiredTime"
  65. type="datetime"
  66. placeholder="请选择失效时间"
  67. class="w100"
  68. value-format="YYYY-MM-DD[T]HH:mm:ss"
  69. :disabled-date="disabledDate"
  70. popper-class="no-atTheMoment"
  71. />
  72. </el-form-item>
  73. </template>
  74. </el-skeleton>
  75. </el-col>
  76. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  77. <el-skeleton :loading="state.loading" animated>
  78. <template #template>
  79. <el-form-item label="案例标题">
  80. <el-skeleton-item variant="h1" />
  81. </el-form-item>
  82. </template>
  83. <template #default>
  84. <el-form-item label="案例标题" prop="title" :rules="[{ required: true, validator: validatePassTitle, trigger: 'blur' }]">
  85. <el-input v-model="state.ruleForm.title" placeholder="请填写案例标题" clearable @blur="isRepeat('title')"></el-input>
  86. </el-form-item>
  87. </template>
  88. </el-skeleton>
  89. </el-col>
  90. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  91. <el-skeleton :loading="state.loading" animated>
  92. <template #template>
  93. <el-form-item label="案例摘要">
  94. <el-skeleton-item variant="h1" />
  95. </el-form-item>
  96. </template>
  97. <template #default>
  98. <el-form-item label="案例摘要" prop="abstract" :rules="[{ required: false, message: '请填写案例摘要', trigger: 'blur' }]">
  99. <el-input v-model="state.ruleForm.abstract" placeholder="请填写案例摘要" clearable></el-input>
  100. </el-form-item>
  101. </template>
  102. </el-skeleton>
  103. </el-col>
  104. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  105. <el-skeleton :loading="state.loading" animated>
  106. <template #template>
  107. <el-form-item label="关键词">
  108. <el-skeleton-item variant="h1" />
  109. </el-form-item>
  110. </template>
  111. <template #default>
  112. <el-form-item label="关键词" prop="keyword" :rules="[{ required: false, message: '请填写关键词', trigger: 'blur' }]">
  113. <el-input v-model="state.ruleForm.keyword" placeholder="请填写关键词" clearable></el-input>
  114. </el-form-item>
  115. </template>
  116. </el-skeleton>
  117. </el-col>
  118. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  119. <el-skeleton :loading="state.loading" animated>
  120. <template #template>
  121. <el-form-item label="关联工单">
  122. <el-skeleton-item variant="h1" />
  123. </el-form-item>
  124. </template>
  125. <template #default>
  126. <el-form-item label="关联工单" prop="keyword" :rules="[{ required: true, message: '请选择关联工单', trigger: 'change' }]">
  127. </el-form-item>
  128. </template>
  129. </el-skeleton>
  130. </el-col>
  131. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  132. <el-skeleton :loading="state.loading" animated>
  133. <template #template>
  134. <el-form-item label="关联知识">
  135. <el-skeleton-item variant="h1" />
  136. </el-form-item>
  137. </template>
  138. <template #default>
  139. <el-form-item label="关联知识" prop="keyword" :rules="[{ required: false, message: '请选择关联知识', trigger: 'blur' }]">
  140. </el-form-item>
  141. </template>
  142. </el-skeleton>
  143. </el-col>
  144. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  145. <el-skeleton :loading="state.loading" animated>
  146. <template #template>
  147. <el-form-item label="案例描述">
  148. <el-skeleton-item variant="h1" />
  149. </el-form-item>
  150. </template>
  151. <template #default>
  152. <el-form-item label="案例描述" prop="describe" :rules="[{ required: true, message: '请填写案例描述', trigger: 'blur' }]">
  153. <editor v-model:get-html="state.ruleForm.describe" placeholder="请填写案例描述" />
  154. </el-form-item>
  155. </template>
  156. </el-skeleton>
  157. </el-col>
  158. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  159. <el-skeleton :loading="state.loading" animated>
  160. <template #template>
  161. <el-form-item label="案例办理结果">
  162. <el-skeleton-item variant="h1" />
  163. </el-form-item>
  164. </template>
  165. <template #default>
  166. <el-form-item label="案例办理结果" prop="result" :rules="[{ required: true, message: '请填写案例办理结果', trigger: 'blur' }]">
  167. <editor v-model:get-html="state.ruleForm.result" placeholder="请填写案例办理结果" />
  168. </el-form-item>
  169. </template>
  170. </el-skeleton>
  171. </el-col>
  172. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  173. <el-skeleton :loading="state.loading" animated>
  174. <template #template>
  175. <el-form-item label="案例描述">
  176. <el-skeleton-item variant="h1" />
  177. </el-form-item>
  178. </template>
  179. <template #default>
  180. <el-form-item label="推荐理由" prop="reason" :rules="[{ required: false, message: '请填写推荐理由', trigger: 'blur' }]">
  181. <editor v-model:get-html="state.ruleForm.reason" placeholder="请填写推荐理由" />
  182. </el-form-item>
  183. </template>
  184. </el-skeleton>
  185. </el-col>
  186. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  187. <el-skeleton :loading="state.loading" animated>
  188. <template #template>
  189. <el-form-item label="是否设为热门案例" label-width="130px">
  190. <el-skeleton-item variant="h1" />
  191. </el-form-item>
  192. </template>
  193. <template #default>
  194. <el-form-item
  195. label-width="130px"
  196. label="是否设为热门案例"
  197. prop="isPopular"
  198. :rules="[{ required: false, message: '请选择是否设为热门案例', trigger: 'change' }]"
  199. >
  200. <el-radio-group v-model="state.ruleForm.isPopular">
  201. <el-radio :value="true">是</el-radio>
  202. <el-radio :value="false">否</el-radio>
  203. </el-radio-group>
  204. </el-form-item>
  205. </template>
  206. </el-skeleton>
  207. </el-col>
  208. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  209. <el-skeleton :loading="state.loading" animated>
  210. <template #template>
  211. <el-form-item label="附件">
  212. <el-skeleton-item variant="h1" />
  213. </el-form-item>
  214. </template>
  215. <template #default>
  216. <el-form-item label="附件" prop="files" :rules="[{ required: false, message: '请选择附件', trigger: 'change' }]">
  217. <annex-list
  218. name="案例附件"
  219. v-model="state.ruleForm.files"
  220. v-model:format="filesFormat"
  221. :businessId="state.ruleForm.id"
  222. classify="案例上传"
  223. />
  224. </el-form-item>
  225. </template>
  226. </el-skeleton>
  227. </el-col>
  228. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  229. <el-form-item>
  230. <el-button type="primary" @click="onSubmitReview(ruleFormRef)" :loading="state.loading">提交审核</el-button>
  231. <el-button class="default-button" @click="onPreview" :loading="state.loading">预览 </el-button>
  232. <el-button @click="onSaveOnly(ruleFormRef)" class="default-button" :loading="state.loading">保存为草稿</el-button>
  233. <el-button class="default-button" @click="onCancel" :loading="state.loading">取消 </el-button>
  234. </el-form-item>
  235. </el-col>
  236. </el-row>
  237. </el-form>
  238. </el-card>
  239. <!-- 删除或者更新提交审核 -->
  240. <edit-submit-audit ref="editSubmitAuditRef" @updateList="processSuccess" />
  241. </div>
  242. </template>
  243. <script setup lang="ts" name="caseEdit">
  244. import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
  245. import type { FormInstance } from 'element-plus';
  246. import { ElMessage } from 'element-plus';
  247. import mittBus from '@/utils/mitt';
  248. import { useRoute, useRouter } from 'vue-router';
  249. import { storeToRefs } from 'pinia';
  250. import { useUserInfo } from '@/stores/userInfo';
  251. import { Local } from '@/utils/storage';
  252. import other from '@/utils/other';
  253. import { throttle, transformFile } from '@/utils/tools';
  254. import { disabledDate } from '@/utils/constants';
  255. import { VTreeDrop } from '@wsfe/vue-tree';
  256. import { addCaseDraft, addCaseDraftAudit, caseDetail, caseRepeat, editCaseDraft } from '@/api/case';
  257. import { caseTreeList } from '@/api/case/type';
  258. // 引入组件
  259. const Editor = defineAsyncComponent(() => import('@/components/Editor/index.vue')); // 富文本编辑器
  260. const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue')); // 附件组件
  261. const EditSubmitAudit = defineAsyncComponent(() => import('@/views/case/index/components/Edit-submit-audit.vue')); // 删除或者更新 提交审核
  262. const stores = useUserInfo(); // 用户信息
  263. const { userInfos } = storeToRefs(stores); // 用户信息
  264. // 定义变量内容
  265. const state = reactive<any>({
  266. dialogVisible: false,
  267. ruleForm: {
  268. attribution: '中心案例库', // 案例归属
  269. files: [], // 附件
  270. content: '', // 内容
  271. title: null, // 标题
  272. keyword: null, // 关键词
  273. abstract: null, // 摘要
  274. expiredTime: null, // 失效时间
  275. isPopular: false, // 是否热门
  276. describe: '', // 案例描述
  277. result: '', // 案例办理结果
  278. reason: '', // 推荐理由
  279. },
  280. typeData: [], // 分类
  281. loading: false,
  282. });
  283. const ruleFormRef = ref<any>(); // 表单ref
  284. const validatePassTitle = (rule: any, value: any, callback: any) => {
  285. if (value === '' || value === null) {
  286. callback(new Error('请填写案例标题'));
  287. } else if (Repeat.value) {
  288. callback(new Error('有相似标题,请检查!'));
  289. } else {
  290. callback();
  291. }
  292. };
  293. // 校验标题/摘要/内容是否重复 根据输入标题检测出关键词填入
  294. const Repeat = ref<boolean>(false);
  295. const isRepeat = (type: string) => {
  296. if (state.ruleForm[type]) {
  297. caseRepeat({ [type]: state.ruleForm[type], id: state.ruleForm.id })
  298. .then((res: any) => {
  299. Repeat.value = res.result;
  300. ruleFormRef.value.validateField(type);
  301. })
  302. .catch(() => {
  303. state.ruleForm[type] = '';
  304. });
  305. }
  306. };
  307. // 默认配置
  308. const defaultContent = ref([
  309. {
  310. type: 'paragraph',
  311. children: [{ text: '', fontFamily: '仿宋', fontSize: '20px' }],
  312. lineHeight: '2',
  313. },
  314. ]);
  315. const selectType = (value: any) => {
  316. state.ruleForm.caseTypes = value.map((item: any) => {
  317. return { caseTypeName: item.name, caseTypeId: item.id, caseTypeSpliceName: item.spliceName };
  318. });
  319. state.ruleForm.caseTypeId = value.map((item: any) => item.id);
  320. ruleFormRef.value.validateField('caseTypeId');
  321. };
  322. // 提交审核
  323. const route = useRoute(); // 获取路由
  324. const router = useRouter(); // 路由跳转
  325. const filesFormat = ref<EmptyArrayType>([]); // 附件列表格式化
  326. const editSubmitAuditRef = ref<RefType>(); // 删除或者更新 提交审核
  327. const onSubmitReview = async (formEl: FormInstance | undefined) => {
  328. if (!formEl) return;
  329. await formEl.validate((valid: boolean) => {
  330. if (!valid) return;
  331. state.ruleForm.files = filesFormat.value;
  332. const submitObj = other.deepClone(state.ruleForm);
  333. Reflect.deleteProperty(submitObj, 'creationTime');
  334. //如果已经有ID 说明是已经提交过的数据 更新审核
  335. if (route.params.id) {
  336. editSubmitAuditRef.value.openDialog(submitObj, 'update');
  337. } else {
  338. state.loading = true;
  339. addCaseDraftAudit(submitObj)
  340. .then(() => {
  341. ElMessage.success('新增案例审核成功');
  342. processSuccess();
  343. })
  344. .catch(() => {
  345. state.loading = false;
  346. });
  347. state.loading = false;
  348. }
  349. });
  350. };
  351. // 流程提交成功
  352. const processSuccess = () => {
  353. // 关闭当前 tagsView
  354. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  355. mittBus.emit('clearCache', 'caseManage');
  356. router.push({
  357. path: '/case/index',
  358. });
  359. };
  360. // 预览
  361. const onPreview = () => {
  362. if (route.params.id) {
  363. } else {
  364. state.ruleForm.creatorName = userInfos.value?.name ?? '';
  365. state.ruleForm.creationTime = new Date();
  366. state.ruleForm.creatorOrgName = userInfos.value?.orgName ?? '';
  367. }
  368. Local.set('casePreviewForm', state.ruleForm);
  369. router.push({
  370. name: 'casePreview',
  371. params: {
  372. tagsViewName: '案例预览',
  373. id: '0',
  374. },
  375. });
  376. };
  377. // 保存到草稿箱
  378. const onSaveOnly = throttle(async (formEl: FormInstance | undefined) => {
  379. if (!formEl) return;
  380. await formEl.validate((valid: boolean) => {
  381. if (!valid) return;
  382. state.loading = true;
  383. state.ruleForm.files = filesFormat.value;
  384. const submitObj = other.deepClone(state.ruleForm);
  385. Reflect.deleteProperty(submitObj, 'creationTime');
  386. if (route.params.id) {
  387. // 更新
  388. editCaseDraft(submitObj)
  389. .then(handleSuccess)
  390. .catch(() => {
  391. state.loading = false;
  392. });
  393. } else {
  394. // 新增
  395. addCaseDraft(submitObj)
  396. .then(handleSuccess)
  397. .catch(() => {
  398. state.loading = false;
  399. });
  400. }
  401. });
  402. }, 300);
  403. // 取消
  404. const onCancel = () => {
  405. state.loading = true;
  406. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  407. mittBus.emit('clearCache', 'caseManage');
  408. router.push({
  409. path: '/case/index',
  410. });
  411. state.loading = false;
  412. };
  413. const handleSuccess = () => {
  414. state.loading = false;
  415. ElMessage.success('操作成功');
  416. // 关闭当前 tagsView
  417. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  418. mittBus.emit('clearCache', 'caseManage');
  419. router.push({
  420. path: '/case/index',
  421. });
  422. };
  423. // 获取分类
  424. const getType = async () => {
  425. state.loading = true;
  426. try {
  427. const [typeDataRes] = await Promise.all([caseTreeList({ IsEnable: true })]);
  428. state.typeData = typeDataRes.result ?? [];
  429. state.loading = false;
  430. } catch (error) {
  431. console.log(error);
  432. state.loading = false;
  433. }
  434. };
  435. const editorRef = ref<RefType>();
  436. const getDetail = async () => {
  437. if (route.params.id) {
  438. const res: any = await caseDetail({ id: route.params.id }); //详情
  439. state.ruleForm = res.result ?? {};
  440. state.ruleForm.files = transformFile(state.ruleForm.files);
  441. state.ruleForm.caseTypeId = state.ruleForm.caseTypes?.map((item: any) => item.CaseTypeID);
  442. }
  443. };
  444. onMounted(async () => {
  445. await getType();
  446. await getDetail();
  447. });
  448. </script>
  449. <style lang="scss">
  450. .vtree-tree-drop__wrapper {
  451. width: 100%;
  452. }
  453. </style>