safetyStandardizationSystemManagementDetail.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <template>
  2. <main class="safety-platform-container__main">
  3. <BasicForm
  4. ref="basicFormRef"
  5. :formData="ruleFormData"
  6. :formRules="isViewMode ? undefined : formRules"
  7. :formConfig="computedFormConfig"
  8. >
  9. <template #fileFormat>
  10. <el-radio-group v-model="ruleFormData.fileFormat" :disabled="isViewMode">
  11. <el-radio value="PDF">PDF</el-radio>
  12. <el-radio value="WORD">WORD</el-radio>
  13. </el-radio-group>
  14. </template>
  15. <template #fileUrl>
  16. <!-- <UploadFiles
  17. label="上传文件"
  18. :maxCount="1"
  19. :file-list="ruleFormData.fileUrlList"
  20. :disabled="isViewMode"
  21. :allow-all-file-types="true"
  22. @uploadSuccess="handleUploadSuccess"
  23. /> -->
  24. <UploadFiles
  25. v-if="!isViewMode"
  26. label="上传地址确认书"
  27. :maxCount="1"
  28. :file-list="ruleFormData.fileUrlList"
  29. @uploadSuccess="handleUploadSuccess"
  30. />
  31. <div class="file-list" v-else>
  32. <div v-if="ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0">
  33. <div class="file-item" v-for="file in ruleFormData.fileUrlList" :key="file.fileId">
  34. <span class="file-item--name">{{ file.fileName }}</span>
  35. <div class="file-item--footer">
  36. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  37. >预览</el-button
  38. >
  39. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  40. >下载</el-button
  41. >
  42. </div>
  43. </div>
  44. </div>
  45. <div v-else class="no-attachment">暂无附件</div>
  46. </div>
  47. </template>
  48. <template #content>
  49. <div class="editor-container" v-if="!isViewMode">
  50. <Toolbar style="border-bottom: 1px solid #dcdfe6" :editor="editorRef" />
  51. <Editor
  52. style="height: 400px; overflow-y: auto"
  53. v-model="ruleFormData.content"
  54. mode="default"
  55. :defaultConfig="editorConfig"
  56. @on-created="handleEditorCreated"
  57. @on-change="handleEditorChange"
  58. />
  59. </div>
  60. <div v-html="ruleFormData.content" v-else></div>
  61. </template>
  62. <template #status>
  63. <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
  64. <el-radio :value="1">启用</el-radio>
  65. <el-radio :value="0">禁用</el-radio>
  66. </el-radio-group>
  67. </template>
  68. </BasicForm>
  69. <PreviewOnline ref="previewOnlineRef" />
  70. </main>
  71. <footer class="safety-platform-container__footer">
  72. <el-button @click="router.back()">返回</el-button>
  73. <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
  74. {{ isCreateMode ? '提交' : '保存' }}
  75. </el-button>
  76. </footer>
  77. </template>
  78. <script setup lang="ts">
  79. import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
  80. import { useRoute, useRouter } from 'vue-router';
  81. import { ElMessage } from 'element-plus';
  82. import BasicForm from '@/components/BasicForm.vue';
  83. import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
  84. import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
  85. import '@wangeditor/editor/dist/css/style.css';
  86. import { useFormConfigHook } from '@/hooks/useFormConfigHook';
  87. import {
  88. SAFETY_STANDARDIZATION_FORM_CONFIG,
  89. SAFETY_STANDARDIZATION_FORM_DATA,
  90. SAFETY_STANDARDIZATION_FORM_RULES,
  91. } from '../configs/form';
  92. import {
  93. querySafetyStandardizationById,
  94. saveSafetyStandardization,
  95. updateSafetyStandardization,
  96. type ProductionSafetyFile,
  97. } from '@/api/production-safety-system';
  98. import type { FileItem } from '@/components/UploadFiles/types';
  99. import { formatAttachmentList } from '@/components/UploadFiles/utils';
  100. import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
  101. import { downloadFile } from '@/views/disaster/utils';
  102. const router = useRouter();
  103. const route = useRoute();
  104. const operate = computed(() => (route.query.operate as string) || 'safety-standardization-create');
  105. const currentId = computed(() => Number(route.query.id));
  106. const isCreateMode = computed(() => operate.value === 'safety-standardization-create');
  107. const isEditMode = computed(() => operate.value === 'safety-standardization-edit');
  108. const isViewMode = computed(() => operate.value === 'safety-standardization-view');
  109. const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
  110. const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } = useFormConfigHook(
  111. SAFETY_STANDARDIZATION_FORM_CONFIG,
  112. SAFETY_STANDARDIZATION_FORM_DATA,
  113. SAFETY_STANDARDIZATION_FORM_RULES,
  114. );
  115. // 查看模式下,所有字段设为只读
  116. const viewFormConfig = ref(
  117. SAFETY_STANDARDIZATION_FORM_CONFIG.map((item) => ({
  118. ...item,
  119. componentProps: {
  120. ...item.componentProps,
  121. disabled: true,
  122. },
  123. })),
  124. );
  125. const computedFormConfig = computed(() => {
  126. if (isViewMode.value) {
  127. return viewFormConfig.value;
  128. }
  129. return ruleFormConfig.value;
  130. });
  131. const basicFormRef = ref<InstanceType<typeof BasicForm>>();
  132. // 富文本编辑器
  133. const editorRef = shallowRef();
  134. const editorConfig = computed(() => ({
  135. placeholder: '请输入文档内容',
  136. MENU_CONF: {},
  137. }));
  138. const handleEditorCreated = (editor: any) => {
  139. editorRef.value = editor;
  140. };
  141. const handleEditorChange = () => {
  142. // 编辑器内容变化时的处理
  143. };
  144. // 文件上传
  145. const handleUploadSuccess = (files: FileItem[]) => {
  146. ruleFormData.fileUrlList = files;
  147. ruleFormData.fileUrl = JSON.stringify(files) || '';
  148. };
  149. // 将逗号分隔的URL字符串转换为FileItem数组
  150. const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
  151. if (!fileUrl || !fileUrl.trim()) {
  152. return [];
  153. }
  154. // 按逗号分割URL
  155. const urls = fileUrl
  156. .split(',')
  157. .map((url) => url.trim())
  158. .filter((url) => url);
  159. return urls.map((url, index) => {
  160. // 从URL中提取文件名
  161. const urlParts = url.split('/');
  162. const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
  163. // 根据文件扩展名判断文件类型
  164. const extension = fileName.split('.').pop()?.toLowerCase() || '';
  165. let fileType = 'pdf';
  166. if (extension === 'doc' || extension === 'docx') {
  167. fileType = 'word';
  168. } else if (extension === 'xls' || extension === 'xlsx') {
  169. fileType = 'excel';
  170. } else if (extension === 'ppt' || extension === 'pptx') {
  171. fileType = 'ppt';
  172. }
  173. return {
  174. fileId: Date.now() + index,
  175. fileName,
  176. fileType,
  177. fileSize: '0',
  178. fileUrl: url,
  179. };
  180. });
  181. };
  182. const handleValidate = async () => {
  183. if (!basicFormRef.value) return;
  184. const res = await basicFormRef.value.validateForm();
  185. return res;
  186. };
  187. const getDetail = async () => {
  188. if (!currentId.value) return;
  189. try {
  190. const res = await querySafetyStandardizationById(currentId.value);
  191. if (res) {
  192. // 映射接口字段到表单字段
  193. ruleFormData.fileName = res.fileName || '';
  194. ruleFormData.classifyName = res.classifyName || '';
  195. ruleFormData.fileCode = res.fileCode || '';
  196. ruleFormData.fileVersion = res.fileVersion || '';
  197. ruleFormData.fileFormat = res.fileFormat || '';
  198. ruleFormData.releaseDate = res.releaseDate || '';
  199. ruleFormData.fileUrl = res.fileUrl || '';
  200. ruleFormData.content = res.content || '';
  201. ruleFormData.status = res.status ?? 1;
  202. ruleFormData.fileUrlList = JSON.parse(res.fileUrl || '[]');
  203. }
  204. cloneRuleFormData();
  205. } catch (e) {
  206. console.error('获取安全标准化体系建设详情失败:', e);
  207. ElMessage.error('获取详情失败');
  208. }
  209. };
  210. const handleSubmit = async () => {
  211. const res = await handleValidate();
  212. if (!res) return;
  213. // 验证文件上传(必填)
  214. if (!ruleFormData.fileUrlList || ruleFormData.fileUrlList.length === 0) {
  215. ElMessage.warning('请上传文件');
  216. return;
  217. }
  218. try {
  219. // 处理文件上传:先上传文件获取 URL,然后提取 fileUrl
  220. // let fileUrl = '';
  221. // if (ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0) {
  222. // // 分离已有URL的文件和新上传的文件
  223. // const existingFiles: string[] = [];
  224. // const newFiles: FileItem[] = [];
  225. // ruleFormData.fileUrlList.forEach((file: FileItem) => {
  226. // // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
  227. // if (file.fileUrl && !file.file) {
  228. // existingFiles.push(file.fileUrl);
  229. // } else {
  230. // // 否则是需要上传的新文件
  231. // newFiles.push(file);
  232. // }
  233. // });
  234. // // 上传新文件
  235. // let uploadedUrls: string[] = [];
  236. // if (newFiles.length > 0) {
  237. // const uploadedFiles = await formatAttachmentList(newFiles);
  238. // uploadedUrls = uploadedFiles.map((file: any) => file.fileUrl || file.url || '').filter((url: string) => url);
  239. // }
  240. // // 合并已有URL和新上传的URL,取第一个作为fileUrl
  241. // const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
  242. // fileUrl = allUrls.length > 0 ? allUrls[0] : '';
  243. // }
  244. const uploadedFileList = await formatAttachmentList(ruleFormData.fileUrlList);
  245. const basePayload: ProductionSafetyFile = {
  246. fileName: ruleFormData.fileName,
  247. classifyName: ruleFormData.classifyName,
  248. fileCode: ruleFormData.fileCode,
  249. fileVersion: ruleFormData.fileVersion,
  250. fileFormat: ruleFormData.fileFormat,
  251. releaseDate: ruleFormData.releaseDate,
  252. fileUrl: JSON.stringify(uploadedFileList) || undefined,
  253. content: ruleFormData.content || undefined,
  254. status: ruleFormData.status ?? 1,
  255. };
  256. if (isCreateMode.value) {
  257. await saveSafetyStandardization(basePayload);
  258. ElMessage.success('创建成功');
  259. } else if (isEditMode.value && currentId.value) {
  260. await updateSafetyStandardization({
  261. id: currentId.value,
  262. ...basePayload,
  263. });
  264. ElMessage.success('保存成功');
  265. }
  266. cloneRuleFormData();
  267. router.back();
  268. } catch (e) {
  269. console.error('保存安全标准化体系建设失败:', e);
  270. ElMessage.error('保存失败,请重试');
  271. }
  272. };
  273. const previewOnline = (url: string | undefined, type) => {
  274. if (url) {
  275. previewOnlineRef.value?.open(url, type);
  276. }
  277. };
  278. onMounted(() => {
  279. cloneRuleFormData();
  280. beforeRouteLeave();
  281. if (isEditMode.value || isViewMode.value) {
  282. getDetail();
  283. }
  284. });
  285. onBeforeUnmount(() => {
  286. const editor = editorRef.value;
  287. if (editor == null) return;
  288. editor.destroy();
  289. });
  290. </script>
  291. <style scoped lang="scss">
  292. @use '@/styles/page-details-layout.scss' as *;
  293. .editor-container {
  294. width: 100%;
  295. border: 1px solid #dcdfe6;
  296. border-radius: 4px;
  297. overflow: hidden;
  298. }
  299. .content-display {
  300. min-height: 200px;
  301. padding: 12px;
  302. border: 1px solid #dcdfe6;
  303. border-radius: 4px;
  304. background-color: #f5f7fa;
  305. }
  306. .file-display {
  307. .file-link {
  308. color: #409eff;
  309. text-decoration: none;
  310. &:hover {
  311. text-decoration: underline;
  312. }
  313. }
  314. }
  315. .no-file {
  316. color: rgba(0, 0, 0, 0.65);
  317. }
  318. </style>