fillingIssued.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. <template>
  2. <div class="app-container p-2">
  3. <el-row :gutter="20" class="mb8" style="margin-top: 5px">
  4. <el-col :span="1.5">
  5. <el-upload class="upload-demo" :http-request="handleFileUpload" :file-list="fileList" accept=".xlsx, .xls">
  6. <template #trigger>
  7. <el-button type="primary">选择Excel文件</el-button>
  8. </template>
  9. </el-upload>
  10. </el-col>
  11. <el-col :span="1.5">
  12. <el-button type="primary" @click="handleNewTemplate">空白模板</el-button>
  13. </el-col>
  14. <el-col :span="1.5">
  15. <el-button type="primary" @click="handleNewTemplate">重新加载</el-button>
  16. </el-col>
  17. <el-col :span="1.5">
  18. <el-button type="primary" @click="handleSaveTemporarily(1)">暂存</el-button>
  19. </el-col>
  20. <el-col :span="1.5">
  21. <el-button type="primary" @click="handleSave(formRef, 2)"> 发布 </el-button>
  22. </el-col>
  23. <el-col :span="1.5">
  24. <el-button type="danger" @click="handleReturn()"> 返回 </el-button>
  25. </el-col>
  26. </el-row>
  27. <el-form ref="formRef" :model="form" :rules="rules">
  28. <el-row :gutter="20">
  29. <el-col :lg="30" :xs="24" style="margin-top: -10px">
  30. <el-row :span="24" :gutter="10">
  31. <!-- 联系人姓名 -->
  32. <el-col :span="8">
  33. <el-form-item label="联系人姓名:" prop="creator_name" label-width="auto">
  34. <el-input v-model="form.creator_name" placeholder="请输入联系人姓名" style="width: 300px"></el-input>
  35. </el-form-item>
  36. </el-col>
  37. <!-- 联系电话 -->
  38. <el-col :span="8">
  39. <el-form-item label="联&nbsp系&nbsp电&nbsp话:" prop="creator_phone" label-width="auto">
  40. <el-input v-model="form.creator_phone" placeholder="请输入联系电话" style="width: 300px"></el-input>
  41. </el-form-item>
  42. </el-col>
  43. <!-- 截止时间 -->
  44. <el-col :span="8">
  45. <el-form-item label="截&nbsp止&nbsp时&nbsp间&nbsp:" prop="end_time">
  46. <el-date-picker
  47. v-model="form.end_time"
  48. value-format="YYYY-MM-DD HH:mm:ss"
  49. time-format="HH:mm"
  50. type="datetime"
  51. placeholder="选择截止时间"
  52. style="width: 300px"
  53. @change="handleTimeChange"
  54. />
  55. </el-form-item>
  56. </el-col>
  57. <!-- 操作按钮 -->
  58. <el-col :span="8">
  59. <el-form-item label="表&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp名:" prop="table_name" label-width="auto">
  60. <el-input v-model="form.table_name" placeholder="请输入表名" style="width: 300px"></el-input>
  61. </el-form-item>
  62. </el-col>
  63. <!-- 选择填报人 -->
  64. <el-col :span="16">
  65. <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold">
  66. <div>
  67. <el-tag
  68. v-for="tag in selectedReporter"
  69. :key="tag"
  70. closable
  71. :disable-transitions="false"
  72. style="margin-right: 10px"
  73. @close="handleClose(tag)"
  74. >
  75. {{ tag.label }}
  76. </el-tag>
  77. </div>
  78. <el-button @click="showSelect">点击选择</el-button>
  79. </el-form-item>
  80. </el-col>
  81. <!-- <el-col :span="8">-->
  82. <!-- <el-button type="primary" @click="handleReport()"> 智能识别 </el-button>-->
  83. <!-- </el-col>-->
  84. </el-row>
  85. </el-col>
  86. </el-row>
  87. </el-form>
  88. <div style="height: 350px">
  89. <hot-table v-if="showTable" ref="wrapper" :data="hotData" :settings="hotSettings" />
  90. </div>
  91. </div>
  92. <informantSelect
  93. v-model="isShowSelect"
  94. v-isShowSelect
  95. :tree-data="treeData"
  96. :default-check-data="form.user_ids"
  97. @confirm="handleContactSelectData"
  98. />
  99. </template>
  100. <script setup lang="ts">
  101. import * as XLSX from 'xlsx';
  102. import { fillingAdd, fillingList } from '@/api/dataFilling/fillingManage';
  103. import { HotTable } from '@handsontable/vue3';
  104. import 'handsontable/languages';
  105. import 'handsontable/dist/handsontable.full.css';
  106. import { registerAllModules } from 'handsontable/registry';
  107. import informantSelect from './informantSelect.vue';
  108. import { getPhoneList } from '@/api/informationissue/informationissue';
  109. import { deepClone } from '@/utils';
  110. import { validatePhone } from '@/utils/validate';
  111. registerAllModules();
  112. const props = defineProps<{
  113. id: string | number;
  114. }>();
  115. const fileList = ref([]);
  116. const { proxy } = getCurrentInstance();
  117. const emits = defineEmits(['close', 'confirm']);
  118. const formRef = ref('');
  119. const detailData = ref({
  120. title: '表单数据',
  121. start: '2024-10-15 17:02:22',
  122. end: '2024-10-15 18:00:00'
  123. });
  124. const hotData = ref([]);
  125. const field_names = ref([]);
  126. const selectedReporter = ref(null);
  127. const isShowSelect = ref(false);
  128. let treeData = ref([]);
  129. // 初始化表格数据
  130. onMounted(async () => {
  131. await fetchData();
  132. await fetchTreeData();
  133. });
  134. const showTable = ref(false);
  135. const handleNewTemplate = () => {
  136. showTable.value = true;
  137. created();
  138. };
  139. const form = ref({
  140. table_name: '',
  141. end_time: '',
  142. status: '0',
  143. issued_status: '',
  144. period_type: '',
  145. creator_name: '',
  146. creator_phone: '',
  147. field_names: [],
  148. user_ids: []
  149. });
  150. const rules = {
  151. table_name: [{ required: true, message: '表名不能为空', trigger: 'blur' }],
  152. end_time: [{ required: true, message: '截止时间不能为空', trigger: 'blur' }],
  153. creator_name: [{ required: true, message: '联系人姓名不能为空', trigger: 'blur' }],
  154. creator_phone: [
  155. { required: true, message: '联系电话不能为空', trigger: 'blur' },
  156. { validator: validatePhone, message: '请输入正确格式的联系电话', trigger: 'blur' }
  157. ],
  158. user_ids: [{ required: true, message: '请选择填报人', trigger: 'blur' }]
  159. };
  160. const handleSaveTemporarily = async (statuCode) => {
  161. if (hotData.value && hotData.value[0] && hotData.value[0].length > 0) {
  162. const data2 = [];
  163. hotData.value[0].forEach((item) => {
  164. if (!!item) {
  165. data2.push(item);
  166. }
  167. });
  168. form.value.field_names = data2;
  169. }
  170. form.value.issued_status = statuCode;
  171. fillingAdd(form.value).then(() => {
  172. proxy.$modal.msgSuccess('暂存成功');
  173. emits('close');
  174. });
  175. };
  176. const handleSave = async (formEl, statuCode) => {
  177. if (!formEl) return;
  178. await formEl.validate((valid, fields) => {
  179. if (valid) {
  180. const data2 = [];
  181. hotData.value[0].forEach((item) => {
  182. if (!!item) {
  183. data2.push(item);
  184. }
  185. });
  186. form.value.field_names = data2;
  187. form.value.issued_status = statuCode;
  188. fillingAdd(form.value).then((res) => {
  189. proxy.$modal.msgSuccess('发布成功');
  190. emits('close');
  191. });
  192. } else {
  193. nextTick(() => {
  194. let isError = document.getElementsByClassName('is-error');
  195. isError[0].scrollIntoView({
  196. // 滚动到指定节点
  197. // 值有start,center,end,nearest,当前显示在视图区域中间
  198. block: 'center',
  199. // 值有auto、instant,smooth,缓动动画(当前是慢速的)
  200. behavior: 'smooth'
  201. });
  202. });
  203. proxy.$modal.msgError('表单校验失败');
  204. return false;
  205. }
  206. });
  207. };
  208. const handleReturn = () => {
  209. emits('close');
  210. };
  211. const created = () => {
  212. let data = [];
  213. for (let i = 0; i < 10; i++) {
  214. let arr = [];
  215. for (let x = 0; x < 10; x++) {
  216. arr.push('');
  217. }
  218. data.push(arr);
  219. }
  220. showTable.value = false;
  221. nextTick(() => {
  222. hotData.value = data;
  223. showTable.value = true;
  224. });
  225. };
  226. const hotSettings = reactive({
  227. language: 'zh-CN',
  228. colHeaders: true,
  229. rowHeaders: true,
  230. autoColumnSize: true,
  231. width: '100%', // auto or 100%
  232. height: '100%', // auto or 100%
  233. licenseKey: 'non-commercial-and-evaluation', // 隐藏版权文字
  234. colWidths: 129, // 默认单元格宽度
  235. rowHeights: 28, // 默认单元格高度
  236. wordWrap: true, // 单元格文字是否换行展示
  237. contextMenu: {
  238. // 自定义右键菜单
  239. items: {
  240. 'row_above': {
  241. name: '向上插一行'
  242. },
  243. 'row_below': {
  244. name: '向下插一行'
  245. },
  246. 'col_left': {
  247. name: '向左插一列'
  248. },
  249. 'col_right': {
  250. name: '向右插一列'
  251. },
  252. 'hsep1': '---------', // 分隔线
  253. 'remove_row': {
  254. name: '删除当前行'
  255. },
  256. 'remove_col': {
  257. name: '删除当前列'
  258. },
  259. 'clear_column': {
  260. name: '清空当前列'
  261. },
  262. 'hsep2': '---------', // 必须和上次的变量名不一样
  263. 'undo': {
  264. name: '撤销'
  265. },
  266. 'cut': {
  267. name: '剪切'
  268. },
  269. 'copy': {
  270. name: '复制'
  271. },
  272. // 'alignment': {
  273. // name: '对齐'
  274. // },
  275. // 'hsep3': '---------',
  276. // 'commentsAddEdit': {
  277. // // 必须开启 comments: true
  278. // name: '添加备注'
  279. // },
  280. // 'commentsRemove': {
  281. // // 必须开启 comments: true
  282. // name: '删除备注'
  283. // },
  284. // 'freeze_column': {
  285. // // 必须开启 manualColumnFreeze: true
  286. // name: '固定列'
  287. // },
  288. // 'unfreeze_column': {
  289. // // 必须开启 manualColumnFreeze: true
  290. // name: '取消固定列'
  291. // }
  292. }
  293. }
  294. });
  295. const handleFileUpload = ({ file }) => {
  296. const reader = new FileReader();
  297. reader.onload = (event) => {
  298. showTable.value = false;
  299. nextTick(() => {
  300. const data = new Uint8Array(event.target.result);
  301. const workbook = XLSX.read(data, { type: 'array' });
  302. const firstSheetName = workbook.SheetNames[0];
  303. const worksheet = workbook.Sheets[firstSheetName];
  304. const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
  305. hotData.value = jsonData.slice(1); // 假设第一行是标题行,我们跳过它
  306. showTable.value = true;
  307. });
  308. };
  309. reader.readAsArrayBuffer(file);
  310. fileList.value = [];
  311. return { success: true, file }; // 告诉 el-upload 上传成功(尽管实际上没有发送到服务器)
  312. };
  313. const showSelect = () => {
  314. isShowSelect.value = true;
  315. };
  316. const fetchTreeData = () => {
  317. return getPhoneList().then((response) => {
  318. treeData.value = response.data;
  319. if (form.value && form.value.user_ids) {
  320. let arr = [];
  321. form.value.user_ids.forEach((item) => {
  322. let res = findNodeById(treeData.value, item);
  323. if (!!res) {
  324. arr.push(res);
  325. }
  326. });
  327. selectedReporter.value = arr;
  328. }
  329. });
  330. };
  331. const handleContactSelectData = (data) => {
  332. selectedReporter.value = deepClone(data);
  333. const data1 = [];
  334. data.forEach((item) => {
  335. data1.push(item.id);
  336. });
  337. form.value.user_ids = data1;
  338. };
  339. const handleClose = (tag: string) => {
  340. selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1);
  341. form.value.user_ids.splice(form.value.user_ids.indexOf(tag.id), 1);
  342. };
  343. const fetchData = () => {
  344. return fillingList(props.id).then((res) => {
  345. form.value.creator_name = res.report_info.creator_name;
  346. form.value.creator_phone = res.report_info.creator_phone;
  347. form.value.end_time = res.report_info.end_time;
  348. form.value.table_name = res.report_info.table_name;
  349. form.value.user_ids = res.report_info.user_ids;
  350. });
  351. };
  352. function findNodeById(data, targetId) {
  353. for (const node of data) {
  354. if (node.id === targetId) {
  355. return { id: node.id, label: node.label }; // 找到匹配节点,直接返回
  356. }
  357. if (node.children && node.children.length > 0) {
  358. const found = findNodeById(node.children, targetId); // 递归搜索子节点
  359. if (found) return { id: found.id, label: found.label }; // 子节点中找到则返回
  360. }
  361. }
  362. return null; // 未找到返回 null
  363. }
  364. </script>
  365. <style scoped>
  366. .app-container {
  367. font-family: Avenir, Helvetica, Arial, sans-serif;
  368. -webkit-font-smoothing: antialiased;
  369. -moz-osx-font-smoothing: grayscale;
  370. text-align: center;
  371. color: #2c3e50;
  372. }
  373. .report-period {
  374. margin-top: 10px;
  375. font-size: 14px;
  376. color: #606266;
  377. }
  378. .editable-span {
  379. cursor: pointer;
  380. display: inline-block;
  381. white-space: nowrap;
  382. overflow: hidden;
  383. text-overflow: ellipsis;
  384. }
  385. .editable-span[contenteditable='true'] {
  386. white-space: normal;
  387. outline: none; /* 移除编辑时的焦点边框 */
  388. }
  389. .editable-span[contenteditable='true']:empty::before {
  390. content: attr(data-placeholder); /* 可选:为空时显示占位符 */
  391. color: #999;
  392. }
  393. .editable-header {
  394. cursor: pointer;
  395. display: inline-block;
  396. white-space: nowrap;
  397. overflow: hidden;
  398. text-overflow: ellipsis;
  399. }
  400. .editable-header[contenteditable='true'] {
  401. white-space: normal;
  402. outline: none; /* 移除编辑时的焦点边框 */
  403. }
  404. .editable-header[contenteditable='true']:empty::before {
  405. content: attr(data-placeholder); /* 可选:为空时显示占位符 */
  406. color: #999;
  407. }
  408. .upload-demo {
  409. display: inline-block;
  410. margin-bottom: 20px;
  411. }
  412. .flex {
  413. flex-wrap: wrap;
  414. }
  415. </style>