safetyTrainingDetail.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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. :fileList="uploadFileList"
  20. @uploadSuccess="handleUploadSuccess"
  21. />
  22. </template>
  23. <template #content>
  24. <div class="editor-container">
  25. <Toolbar
  26. style="border-bottom: 1px solid #dcdfe6"
  27. :editor="editorRef"
  28. />
  29. <Editor
  30. style="height: 400px; overflow-y: auto"
  31. v-model="ruleFormData.content"
  32. mode="default"
  33. :defaultConfig="editorConfig"
  34. @on-created="handleEditorCreated"
  35. @on-change="handleEditorChange"
  36. />
  37. </div>
  38. </template>
  39. <template #status>
  40. <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
  41. <el-radio :value="1">启用</el-radio>
  42. <el-radio :value="0">禁用</el-radio>
  43. </el-radio-group>
  44. </template>
  45. </BasicForm>
  46. </main>
  47. <footer class="safety-platform-container__footer">
  48. <el-button @click="router.back()">返回</el-button>
  49. <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
  50. {{ isCreateMode ? '提交' : '保存' }}
  51. </el-button>
  52. </footer>
  53. </template>
  54. <script setup lang="ts">
  55. import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
  56. import { useRoute, useRouter } from 'vue-router';
  57. import { ElMessage } from 'element-plus';
  58. import BasicForm from '@/components/BasicForm.vue';
  59. import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
  60. import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
  61. import '@wangeditor/editor/dist/css/style.css';
  62. import { useFormConfigHook } from '@/hooks/useFormConfigHook';
  63. import {
  64. INDUSTRY_STANDARD_FORM_CONFIG,
  65. INDUSTRY_STANDARD_FORM_DATA,
  66. INDUSTRY_STANDARD_FORM_RULES,
  67. } from '../configs/form';
  68. import {
  69. queryIndustryStandardById,
  70. saveIndustryStandard,
  71. updateIndustryStandard,
  72. type ProductionSafetyFile,
  73. } from '@/api/production-safety-system';
  74. import type { FileItem } from '@/components/UploadFiles/types';
  75. const router = useRouter();
  76. const route = useRoute();
  77. const operate = computed(() => (route.query.operate as string) || 'industry-standard-create');
  78. const currentId = computed(() => Number(route.query.id));
  79. const isCreateMode = computed(() => operate.value === 'industry-standard-create');
  80. const isEditMode = computed(() => operate.value === 'industry-standard-edit');
  81. const isViewMode = computed(() => operate.value === 'industry-standard-view');
  82. const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } = useFormConfigHook(
  83. INDUSTRY_STANDARD_FORM_CONFIG,
  84. INDUSTRY_STANDARD_FORM_DATA,
  85. INDUSTRY_STANDARD_FORM_RULES,
  86. );
  87. // 查看模式下,所有字段设为只读
  88. const viewFormConfig = ref(
  89. INDUSTRY_STANDARD_FORM_CONFIG.map((item) => ({
  90. ...item,
  91. componentProps: {
  92. ...item.componentProps,
  93. disabled: true,
  94. },
  95. })),
  96. );
  97. const computedFormConfig = computed(() => {
  98. if (isViewMode.value) {
  99. return viewFormConfig.value;
  100. }
  101. return ruleFormConfig.value;
  102. });
  103. const basicFormRef = ref<InstanceType<typeof BasicForm>>();
  104. // 富文本编辑器
  105. const editorRef = shallowRef();
  106. const editorConfig = computed(() => ({
  107. placeholder: '请输入文档内容',
  108. MENU_CONF: {},
  109. }));
  110. const handleEditorCreated = (editor: any) => {
  111. editorRef.value = editor;
  112. };
  113. const handleEditorChange = () => {
  114. // 编辑器内容变化时的处理
  115. };
  116. // 文件上传
  117. const uploadFileList = ref<FileItem[]>([]);
  118. const handleUploadSuccess = (files: FileItem[]) => {
  119. uploadFileList.value = files;
  120. if (files.length > 0 && files[0].file) {
  121. // 这里需要实际上传文件到服务器,获取 fileUrl
  122. // 暂时使用文件对象,实际应该调用上传接口
  123. ruleFormData.fileUrl = files[0].file.name; // 临时处理,需要替换为实际上传后的URL
  124. }
  125. };
  126. const getFileName = (url: string) => {
  127. if (!url) return '';
  128. const parts = url.split('/');
  129. return parts[parts.length - 1];
  130. };
  131. const handleValidate = async () => {
  132. if (!basicFormRef.value) return;
  133. const res = await basicFormRef.value.validateForm();
  134. return res;
  135. };
  136. const getDetail = async () => {
  137. if (!currentId.value) return;
  138. try {
  139. const res = await queryIndustryStandardById(currentId.value);
  140. if (res) {
  141. // 映射接口字段到表单字段
  142. ruleFormData.fileName = res.fileName || '';
  143. ruleFormData.classifyName = res.classifyName || '';
  144. ruleFormData.fileCode = res.fileCode || '';
  145. ruleFormData.fileVersion = res.fileVersion || '';
  146. ruleFormData.fileFormat = res.fileFormat || '';
  147. ruleFormData.releaseDate = res.releaseDate || '';
  148. ruleFormData.fileUrl = res.fileUrl || '';
  149. ruleFormData.content = res.content || '';
  150. ruleFormData.status = res.status ?? 1;
  151. // 如果有文件URL,转换为FileItem格式
  152. if (res.fileUrl) {
  153. uploadFileList.value = [
  154. {
  155. fileId: Date.now(),
  156. fileName: getFileName(res.fileUrl),
  157. fileType: res.fileFormat?.toLowerCase() === 'pdf' ? 'pdf' : 'word',
  158. fileSize: '0KB',
  159. },
  160. ];
  161. }
  162. }
  163. cloneRuleFormData();
  164. } catch (e) {
  165. console.error('获取行业标准详情失败:', e);
  166. ElMessage.error('获取详情失败');
  167. }
  168. };
  169. const handleSubmit = async () => {
  170. const res = await handleValidate();
  171. if (!res) return;
  172. try {
  173. const basePayload: ProductionSafetyFile = {
  174. fileName: ruleFormData.fileName,
  175. classifyName: ruleFormData.classifyName,
  176. fileCode: ruleFormData.fileCode,
  177. fileVersion: ruleFormData.fileVersion,
  178. fileFormat: ruleFormData.fileFormat,
  179. releaseDate: ruleFormData.releaseDate,
  180. fileUrl: ruleFormData.fileUrl || undefined,
  181. content: ruleFormData.content || undefined,
  182. status: ruleFormData.status ?? 1,
  183. };
  184. if (isCreateMode.value) {
  185. await saveIndustryStandard(basePayload);
  186. ElMessage.success('创建成功');
  187. } else if (isEditMode.value && currentId.value) {
  188. await updateIndustryStandard({
  189. id: currentId.value,
  190. ...basePayload,
  191. });
  192. ElMessage.success('保存成功');
  193. }
  194. router.back();
  195. } catch (e) {
  196. console.error('保存行业标准失败:', e);
  197. ElMessage.error('保存失败,请重试');
  198. }
  199. };
  200. onMounted(() => {
  201. cloneRuleFormData();
  202. beforeRouteLeave();
  203. if (isEditMode.value || isViewMode.value) {
  204. getDetail();
  205. }
  206. });
  207. onBeforeUnmount(() => {
  208. const editor = editorRef.value;
  209. if (editor == null) return;
  210. editor.destroy();
  211. });
  212. </script>
  213. <style scoped lang="scss">
  214. @use '@/styles/page-details-layout.scss' as *;
  215. .editor-container {
  216. width: 100%;
  217. border: 1px solid #dcdfe6;
  218. border-radius: 4px;
  219. overflow: hidden;
  220. }
  221. .content-display {
  222. min-height: 200px;
  223. padding: 12px;
  224. border: 1px solid #dcdfe6;
  225. border-radius: 4px;
  226. background-color: #f5f7fa;
  227. }
  228. .file-display {
  229. .file-link {
  230. color: #409eff;
  231. text-decoration: none;
  232. &:hover {
  233. text-decoration: underline;
  234. }
  235. }
  236. }
  237. .no-file {
  238. color: rgba(0, 0, 0, 0.65);
  239. }
  240. </style>