edit.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <div class="plan-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="planTypeId" :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.planTypeId"
  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="content" :rules="[{ required: true, validator: validatePassContent, trigger: 'blur' }]">
  99. <editor
  100. v-model:get-html="state.ruleForm.content"
  101. :disable="state.disable"
  102. placeholder="请填写预案内容"
  103. :defaultContent="defaultContent"
  104. ref="editorRef"
  105. @blur="isRepeat('content')"
  106. />
  107. </el-form-item>
  108. </template>
  109. </el-skeleton>
  110. </el-col>
  111. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  112. <el-skeleton :loading="state.loading" animated>
  113. <template #template>
  114. <el-form-item label="附件">
  115. <el-skeleton-item variant="h1" />
  116. </el-form-item>
  117. </template>
  118. <template #default>
  119. <el-form-item label="附件" prop="files" :rules="[{ required: false, message: '请选择附件', trigger: 'change' }]">
  120. <annex-list
  121. name="预案附件"
  122. v-model="state.ruleForm.files"
  123. v-model:format="filesFormat"
  124. :businessId="state.ruleForm.id"
  125. classify="预案上传"
  126. />
  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-form-item>
  133. <el-button type="primary" @click="onSubmitReview(ruleFormRef)" :loading="state.loading">提交审核</el-button>
  134. <el-button class="default-button" @click="onPreview" :loading="state.loading">预览 </el-button>
  135. <el-button @click="onSaveOnly(ruleFormRef)" class="default-button" :loading="state.loading">保存为草稿</el-button>
  136. <el-button class="default-button" @click="onCancel" :loading="state.loading">取消 </el-button>
  137. </el-form-item>
  138. </el-col>
  139. </el-row>
  140. </el-form>
  141. </el-card>
  142. <!-- 删除或者更新提交审核 -->
  143. <edit-submit-audit ref="editSubmitAuditRef" @updateList="processSuccess" />
  144. </div>
  145. </template>
  146. <script setup lang="ts" name="planEdit">
  147. import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
  148. import type { FormInstance } from 'element-plus';
  149. import { ElMessage } from 'element-plus';
  150. import mittBus from '@/utils/mitt';
  151. import { useRoute, useRouter } from 'vue-router';
  152. import { storeToRefs } from 'pinia';
  153. import { useUserInfo } from '@/stores/userInfo';
  154. import { Local } from '@/utils/storage';
  155. import other from '@/utils/other';
  156. import { throttle, transformFile } from '@/utils/tools';
  157. import { disabledDate } from '@/utils/constants';
  158. import { VTreeDrop } from '@wsfe/vue-tree';
  159. import { planTreeList } from '@/api/plan/type';
  160. import { addPlanDraft, addPlanDraftAudit, editPlanDraft, planDetail, planRepeat } from '@/api/plan';
  161. // 引入组件
  162. const Editor = defineAsyncComponent(() => import('@/components/Editor/index.vue')); // 富文本编辑器
  163. const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue')); // 附件组件
  164. const EditSubmitAudit = defineAsyncComponent(() => import('@/views/plan/index/components/Edit-submit-audit.vue')); // 删除或者更新 提交审核
  165. const stores = useUserInfo(); // 用户信息
  166. const { userInfos } = storeToRefs(stores); // 用户信息
  167. // 定义变量内容
  168. const state = reactive<any>({
  169. dialogVisible: false,
  170. ruleForm: {
  171. attribution: '中心预案库', // 预案归属
  172. isPublic: true, // 是否公开
  173. files: [], // 附件
  174. content: '', // 内容
  175. title: null, // 标题
  176. },
  177. typeData: [], // 分类
  178. loading: false,
  179. });
  180. const ruleFormRef = ref<any>(); // 表单ref
  181. const validatePassTitle = (rule: any, value: any, callback: any) => {
  182. if (value === '' || value === null) {
  183. callback(new Error('请填写案例标题'));
  184. } else if (Repeat.value) {
  185. callback(new Error('有相似标题,请检查!'));
  186. } else {
  187. callback();
  188. }
  189. };
  190. const validatePassContent = (rule: any, value: any, callback: any) => {
  191. if (value === '' || value === null) {
  192. callback(new Error('请填写案例内容'));
  193. } else if (Repeat.value) {
  194. callback(new Error('有相似内容,请检查!'));
  195. } else {
  196. callback();
  197. }
  198. };
  199. // 校验标题/摘要/内容是否重复 根据输入标题检测出关键词填入
  200. const Repeat = ref<boolean>(false);
  201. const isRepeat = (type: string) => {
  202. if (state.ruleForm[type]) {
  203. planRepeat({ [type]: state.ruleForm[type], id: state.ruleForm.id })
  204. .then((res: any) => {
  205. Repeat.value = res.result;
  206. ruleFormRef.value.validateField(type);
  207. })
  208. .catch(() => {
  209. state.ruleForm[type] = '';
  210. });
  211. }
  212. };
  213. // 默认配置
  214. const defaultContent = ref([
  215. {
  216. type: 'paragraph',
  217. children: [{ text: '', fontFamily: '仿宋', fontSize: '20px' }],
  218. lineHeight: '2',
  219. },
  220. ]);
  221. const selectType = (value: any) => {
  222. state.ruleForm.planTypes = value.map((item: any) => {
  223. return { planTypeName: item.name, planTypeId: item.id, planTypeSpliceName: item.spliceName };
  224. });
  225. state.ruleForm.planTypeId = value.map((item: any) => item.id);
  226. ruleFormRef.value.validateField('planTypeId');
  227. };
  228. // 提交审核
  229. const route = useRoute(); // 获取路由
  230. const router = useRouter(); // 路由跳转
  231. const filesFormat = ref<EmptyArrayType>([]); // 附件列表格式化
  232. const editSubmitAuditRef = ref<RefType>(); // 删除或者更新 提交审核
  233. const onSubmitReview = async (formEl: FormInstance | undefined) => {
  234. if (!formEl) return;
  235. await formEl.validate((valid: boolean) => {
  236. if (!valid) return;
  237. state.ruleForm.files = filesFormat.value;
  238. const submitObj = other.deepClone(state.ruleForm);
  239. Reflect.deleteProperty(submitObj, 'creationTime');
  240. //如果已经有ID 说明是已经提交过的数据 更新审核
  241. if (route.params.id) {
  242. editSubmitAuditRef.value.openDialog(submitObj, 'update');
  243. } else {
  244. state.loading = true;
  245. addPlanDraftAudit(submitObj)
  246. .then(() => {
  247. ElMessage.success('新增预案审核成功');
  248. processSuccess();
  249. })
  250. .catch(() => {
  251. state.loading = false;
  252. });
  253. state.loading = false;
  254. }
  255. });
  256. };
  257. // 流程提交成功
  258. const processSuccess = () => {
  259. // 关闭当前 tagsView
  260. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  261. mittBus.emit('clearCache', 'planManage');
  262. router.push({
  263. path: '/plan/index',
  264. });
  265. };
  266. // 预览
  267. const onPreview = () => {
  268. if (route.params.id) {
  269. } else {
  270. state.ruleForm.creatorName = userInfos.value?.name ?? '';
  271. state.ruleForm.creationTime = new Date();
  272. state.ruleForm.creatorOrgName = userInfos.value?.orgName ?? '';
  273. }
  274. Local.set('planPreviewForm', state.ruleForm);
  275. router.push({
  276. name: 'planPreview',
  277. params: {
  278. tagsViewName: '案例预览',
  279. id: '0',
  280. },
  281. });
  282. };
  283. // 保存到草稿箱
  284. const onSaveOnly = throttle(async (formEl: FormInstance | undefined) => {
  285. if (!formEl) return;
  286. await formEl.validate((valid: boolean) => {
  287. if (!valid) return;
  288. state.loading = true;
  289. state.ruleForm.files = filesFormat.value;
  290. const submitObj = other.deepClone(state.ruleForm);
  291. Reflect.deleteProperty(submitObj, 'creationTime');
  292. if (route.params.id) {
  293. // 更新
  294. editPlanDraft( submitObj )
  295. .then(handleSuccess)
  296. .catch(() => {
  297. state.loading = false;
  298. });
  299. } else {
  300. // 新增
  301. addPlanDraft(submitObj)
  302. .then(handleSuccess)
  303. .catch(() => {
  304. state.loading = false;
  305. });
  306. }
  307. });
  308. }, 300);
  309. // 取消
  310. const onCancel = () => {
  311. state.loading = true;
  312. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  313. mittBus.emit('clearCache', 'planManage');
  314. router.push({
  315. path: '/plan/index',
  316. });
  317. state.loading = false;
  318. };
  319. const handleSuccess = () => {
  320. state.loading = false;
  321. ElMessage.success('操作成功');
  322. // 关闭当前 tagsView
  323. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  324. mittBus.emit('clearCache', 'planManage');
  325. router.push({
  326. path: '/plan/index',
  327. });
  328. };
  329. // 获取分类
  330. const getType = async () => {
  331. state.loading = true;
  332. try {
  333. const [typeDataRes] = await Promise.all([planTreeList({ IsEnable: true })]);
  334. state.typeData = typeDataRes.result ?? [];
  335. state.loading = false;
  336. } catch (error) {
  337. console.log(error);
  338. state.loading = false;
  339. }
  340. };
  341. const editorRef = ref<RefType>();
  342. const getDetail = async () => {
  343. if (route.params.id) {
  344. const res: any = await planDetail({ id: route.params.id }); //详情
  345. state.ruleForm = res.result ?? {};
  346. state.ruleForm.files = transformFile(state.ruleForm.files);
  347. state.ruleForm.planTypeId = state.ruleForm.planTypes?.map((item: any) => item.planTypeId);
  348. }
  349. };
  350. onMounted(async () => {
  351. await getType();
  352. await getDetail();
  353. });
  354. </script>
  355. <style lang="scss">
  356. .vtree-tree-drop__wrapper {
  357. width: 100%;
  358. }
  359. </style>