safetyStandardizationSystemManagementDetail.vue 9.9 KB

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