edit.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. <template>
  2. <el-card shadow="never" :body-style="{ height: 'calc(100vh - 260px)' }">
  3. <el-scrollbar>
  4. <PlusForm
  5. v-model="props.selProject"
  6. class="w-[960px] m-auto"
  7. :columns="projectColumns"
  8. :rules="rules"
  9. label-position="right"
  10. @change="handleChange"
  11. @submit="handleSubmit"
  12. @submit-error="handleSubmitError"
  13. @reset="handleReset"
  14. >
  15. <template #footer="{ handleSubmit }">
  16. <div>
  17. <el-button
  18. v-if="props.swEditable"
  19. @click="dlgImportExcelVisible = true"
  20. >
  21. 从Excel导入
  22. </el-button>
  23. <el-button @click="handleExport"> 导出到Excel </el-button>
  24. <el-button
  25. v-if="props.swEditable"
  26. type="primary"
  27. @click="handleSubmit"
  28. >提交</el-button
  29. >
  30. </div>
  31. </template>
  32. <template #plus-field-id>
  33. <el-input
  34. v-if="props.selProject.isCreate && swEditable"
  35. v-model="props.selProject.id"
  36. >
  37. </el-input>
  38. <p v-else label="ID" :content="props.selProject.id">
  39. {{ props.selProject.id }}
  40. </p>
  41. </template>
  42. <template #plus-field-name>
  43. <el-input v-if="swEditable" v-model="props.selProject.name">
  44. </el-input>
  45. <p v-else label="name" :content="props.selProject.name">
  46. {{ props.selProject.name }}
  47. </p>
  48. </template>
  49. <template #plus-field-version>
  50. <el-input v-if="swEditable" v-model="props.selProject.version">
  51. </el-input>
  52. <p v-else label="version" :content="props.selProject.version">
  53. {{ props.selProject.version }}
  54. </p>
  55. </template>
  56. <template #plus-field-status>
  57. <el-select v-if="swEditable" v-model="props.selProject.status">
  58. <el-option label="未运行" :value="0"></el-option>
  59. <el-option label="运行中" :value="1"></el-option>
  60. <el-option label="已完成" :value="2"></el-option>
  61. </el-select>
  62. <el-tag
  63. v-else
  64. :type="
  65. props.selProject.status == 0
  66. ? 'info'
  67. : props.selProject.status == 1
  68. ? 'primary'
  69. : 'success'
  70. "
  71. >
  72. {{
  73. props.selProject.status == 0
  74. ? "未运行"
  75. : props.selProject.status == 1
  76. ? "运行中"
  77. : "已完成"
  78. }}
  79. </el-tag>
  80. </template>
  81. <template #plus-field-locations>
  82. <div style="margin-bottom: 10px">
  83. <el-button v-if="swEditable" @click="onAddLocation">
  84. 添加区域
  85. </el-button>
  86. </div>
  87. <pure-table
  88. align-whole="center"
  89. :header-cell-style="{
  90. background: 'var(--el-fill-color-light)',
  91. color: 'var(--el-text-color-primary)'
  92. }"
  93. :columns="locationColumns"
  94. :table-key="tableKey"
  95. border
  96. showOverflowTooltip
  97. :data="locationDataList"
  98. >
  99. <template #operation="{ row, index }">
  100. <div v-if="swEditable && !locationEditMap[index]?.editable">
  101. <el-button
  102. class="reset-margin"
  103. link
  104. type="primary"
  105. :icon="useRenderIcon(Delete)"
  106. @click="onDelLocation(row)"
  107. >
  108. 删除
  109. </el-button>
  110. <el-button
  111. class="reset-margin"
  112. link
  113. type="primary"
  114. @click="onEditLocation(row, index)"
  115. :icon="useRenderIcon(EditFill)"
  116. >
  117. 修改
  118. </el-button>
  119. </div>
  120. <div v-if="swEditable && locationEditMap[index]?.editable">
  121. <el-button
  122. class="reset-margin"
  123. link
  124. type="primary"
  125. @click="onSaveLocation(index)"
  126. :icon="useRenderIcon(SaveFill)"
  127. >
  128. 保存
  129. </el-button>
  130. <el-button
  131. class="reset-margin"
  132. link
  133. @click="onCancelLocation(index)"
  134. :icon="useRenderIcon(Close)"
  135. >
  136. 取消
  137. </el-button>
  138. </div>
  139. </template>
  140. </pure-table>
  141. </template>
  142. <template #plus-field-persons>
  143. <div style="margin-bottom: 10px">
  144. <el-button v-if="swEditable" @click="onAddPerson">
  145. 添加人员
  146. </el-button>
  147. </div>
  148. <pure-table
  149. align-whole="center"
  150. :header-cell-style="{
  151. background: 'var(--el-fill-color-light)',
  152. color: 'var(--el-text-color-primary)'
  153. }"
  154. :columns="personColumns"
  155. :table-key="tableKey"
  156. border
  157. showOverflowTooltip
  158. :data="personDataList"
  159. >
  160. <template #operation="{ row, index }">
  161. <div v-if="swEditable && !personEditMap[index]?.editable">
  162. <el-button
  163. class="reset-margin"
  164. link
  165. type="primary"
  166. :icon="useRenderIcon(Delete)"
  167. @click="onDelPerson(row)"
  168. >
  169. 删除
  170. </el-button>
  171. <el-button
  172. class="reset-margin"
  173. link
  174. type="primary"
  175. @click="onEditPerson(row, index)"
  176. :icon="useRenderIcon(EditFill)"
  177. >
  178. 修改
  179. </el-button>
  180. </div>
  181. <div v-if="swEditable && personEditMap[index]?.editable">
  182. <el-button
  183. class="reset-margin"
  184. link
  185. type="primary"
  186. @click="onSavePerson(index)"
  187. :icon="useRenderIcon(SaveFill)"
  188. >
  189. 保存
  190. </el-button>
  191. <el-button
  192. class="reset-margin"
  193. link
  194. @click="onCancelPerson(index)"
  195. :icon="useRenderIcon(Close)"
  196. >
  197. 取消
  198. </el-button>
  199. </div>
  200. </template>
  201. </pure-table>
  202. </template>
  203. </PlusForm>
  204. </el-scrollbar>
  205. <el-dialog
  206. v-model="dlgImportExcelVisible"
  207. title="Excel导入"
  208. width="60%"
  209. :before-close="handleDlgClose"
  210. destroy-on-close
  211. top="6vh"
  212. >
  213. <importExcel
  214. ref="importExcelRef"
  215. @onExcelDataProject="onExcelDataProject"
  216. />
  217. <template #footer>
  218. <span class="dialog-footer">
  219. <el-button @click="dlgImportExcelVisible = false">取消</el-button>
  220. <el-button type="primary" @click="handleDlgConfirm"> 导入 </el-button>
  221. </span>
  222. </template>
  223. </el-dialog>
  224. </el-card>
  225. </template>
  226. <script setup lang="ts">
  227. import { ref, onMounted } from "vue";
  228. // https://plus-pro-components.com/components/form.html
  229. import "plus-pro-components/es/components/form/style/css";
  230. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  231. import {
  232. type PlusColumn,
  233. type FieldValues,
  234. PlusForm
  235. } from "plus-pro-components";
  236. import { ElNotification, ElMessageBox } from "element-plus";
  237. import { text } from "stream/consumers";
  238. import AddFill from "~icons/ep/plus";
  239. import Delete from "~icons/ep/delete";
  240. import EditFill from "~icons/ep/edit";
  241. import SaveFill from "~icons/bi/save";
  242. import Close from "~icons/ep/close";
  243. import Project from "@/model/project.js";
  244. import { useLocationColumns } from "./locationColumns";
  245. import { usePersonColumns } from "./personColumns";
  246. import ImportExcel from "./importExcel.vue";
  247. import * as XLSX from "xlsx";
  248. const dlgImportExcelVisible = ref(false);
  249. const excelData = ref(null);
  250. const props = defineProps({
  251. // 字符串类型
  252. selProject: {
  253. type: Project,
  254. default: new Project()
  255. },
  256. swEditable: {
  257. type: Boolean,
  258. default: true
  259. }
  260. });
  261. const emit = defineEmits(["onSubmitData"]);
  262. const {
  263. locationEditMap,
  264. locationColumns,
  265. locationDataList,
  266. onEditLocation,
  267. onSaveLocation,
  268. onCancelLocation,
  269. onAddLocation,
  270. onDelLocation
  271. } = useLocationColumns();
  272. const {
  273. personEditMap,
  274. personColumns,
  275. personDataList,
  276. onEditPerson,
  277. onSavePerson,
  278. onCancelPerson,
  279. onAddPerson,
  280. onDelPerson
  281. } = usePersonColumns();
  282. const tableKey = ref<string>("");
  283. const state = ref<FieldValues>({
  284. status: "1",
  285. name: "",
  286. rate: 4,
  287. progress: 100,
  288. switch: true,
  289. time: new Date().toString(),
  290. endTime: []
  291. });
  292. const rules = {
  293. id: [
  294. {
  295. required: true,
  296. message: "请输入ID"
  297. }
  298. ]
  299. };
  300. const projectColumns: PlusColumn[] = [
  301. {
  302. label: "ID",
  303. width: 800,
  304. prop: "id",
  305. valueType: "input",
  306. tooltip: "ID必须填正整数",
  307. colProps: {
  308. span: 8
  309. }
  310. },
  311. {
  312. label: "项目名称",
  313. width: 800,
  314. prop: "name",
  315. valueType: "input",
  316. colProps: {
  317. span: 8
  318. }
  319. },
  320. {
  321. label: "版本",
  322. width: 800,
  323. prop: "version",
  324. valueType: "input",
  325. tooltip: "版本必须是正整数",
  326. colProps: {
  327. span: 8
  328. }
  329. },
  330. {
  331. label: "区域",
  332. width: 800,
  333. prop: "locations",
  334. valueType: "copy"
  335. },
  336. {
  337. label: "人员",
  338. width: 800,
  339. prop: "persons",
  340. valueType: "copy"
  341. },
  342. {
  343. label: "状态",
  344. width: 800,
  345. prop: "status",
  346. valueType: "select",
  347. options: [
  348. {
  349. label: "未运行",
  350. value: 0,
  351. color: "gray"
  352. },
  353. {
  354. label: "运行中",
  355. value: 1,
  356. color: "blue"
  357. },
  358. {
  359. label: "已完成",
  360. value: 2,
  361. color: "green"
  362. }
  363. ]
  364. }
  365. ];
  366. const handleChange = (values: FieldValues, prop: PlusColumn) => {
  367. console.log(values, prop, "change");
  368. };
  369. const handleSubmit = (values: FieldValues) => {
  370. console.log(values, "submit");
  371. props.selProject.locations = locationDataList.value;
  372. props.selProject.persons = personDataList.value;
  373. emit("onSubmitData", props.selProject);
  374. };
  375. const handleSubmitError = (err: any) => {
  376. console.log(err, "err");
  377. };
  378. const handleReset = () => {
  379. console.log("handleReset");
  380. };
  381. const handleExport = () => {
  382. try {
  383. const project = props.selProject;
  384. // 0. 创建工作簿
  385. const workbook = XLSX.utils.book_new();
  386. // 1-1. 准备 Proj参数 数据
  387. let exportData = [
  388. { name: "项目ID", value: project.id },
  389. { name: "项目名称", value: project.name },
  390. { name: "项目版本", value: project.version }
  391. ];
  392. // 1-2. 创建工作表
  393. let worksheet = XLSX.utils.json_to_sheet(exportData);
  394. // 1-3. 计算列宽(关键步骤)
  395. let header = Object.keys(exportData[0]);
  396. let cols = header.map(key => {
  397. // 计算表头文字长度
  398. let maxLength = key.length * 2;
  399. // 计算数据列最大文字长度
  400. exportData.forEach(row => {
  401. const value = String(row[key] || "");
  402. // 中文字符宽度约为英文字符的2倍,这里做特殊处理
  403. const length = value.replace(/[^\x00-\xff]/g, "aa").length;
  404. if (length > maxLength) {
  405. maxLength = length;
  406. }
  407. });
  408. // 增加缓冲宽度(避免内容太满)
  409. return { width: maxLength + 2 };
  410. });
  411. // 设置列宽配置
  412. worksheet["!cols"] = cols;
  413. // 1-4. 创建工作表
  414. XLSX.utils.book_append_sheet(workbook, worksheet, "项目");
  415. // 2-1. 准备 Proj参数 数据
  416. exportData = project.locations.map(item => ({
  417. id: item.id,
  418. name: item.name,
  419. cx: item.center.x,
  420. cy: item.center.y
  421. }));
  422. // 2-2. 创建工作表
  423. worksheet = XLSX.utils.json_to_sheet(exportData);
  424. // 2-3. 计算列宽(关键步骤)
  425. header = Object.keys(exportData[0]);
  426. cols = header.map(key => {
  427. // 计算表头文字长度
  428. let maxLength = key.length * 2;
  429. // 计算数据列最大文字长度
  430. exportData.forEach(row => {
  431. const value = String(row[key] || "");
  432. // 中文字符宽度约为英文字符的2倍,这里做特殊处理
  433. const length = value.replace(/[^\x00-\xff]/g, "aa").length;
  434. if (length > maxLength) {
  435. maxLength = length;
  436. }
  437. });
  438. // 增加缓冲宽度(避免内容太满)
  439. return { width: maxLength + 2 };
  440. });
  441. // 设置列宽配置
  442. worksheet["!cols"] = cols;
  443. // 2-4. 创建工作表
  444. XLSX.utils.book_append_sheet(workbook, worksheet, "区域");
  445. // 3-1. 准备 Proj参数 数据
  446. exportData = project.persons.map(item => ({
  447. id: item.id,
  448. name: item.name,
  449. job: item.job,
  450. phone: item.phone,
  451. locId: item.locId,
  452. tagId: item.tagId
  453. }));
  454. // 3-2. 创建工作表
  455. worksheet = XLSX.utils.json_to_sheet(exportData);
  456. // 3-3. 计算列宽(关键步骤)
  457. header = Object.keys(exportData[0]);
  458. cols = header.map(key => {
  459. // 计算表头文字长度
  460. let maxLength = key.length * 2;
  461. // 计算数据列最大文字长度
  462. exportData.forEach(row => {
  463. const value = String(row[key] || "");
  464. // 中文字符宽度约为英文字符的2倍,这里做特殊处理
  465. const length = value.replace(/[^\x00-\xff]/g, "aa").length;
  466. if (length > maxLength) {
  467. maxLength = length;
  468. }
  469. });
  470. // 增加缓冲宽度(避免内容太满)
  471. return { width: maxLength + 2 };
  472. });
  473. // 设置列宽配置
  474. worksheet["!cols"] = cols;
  475. // 3-4. 创建工作表
  476. XLSX.utils.book_append_sheet(workbook, worksheet, "人员");
  477. // 4. 生成Excel文件并下载
  478. const fileName = `项目_${project.name}_${new Date().getTime()}.xlsx`;
  479. XLSX.writeFile(workbook, fileName);
  480. } catch (error) {
  481. console.error("导出失败:", error);
  482. ElNotification.error({
  483. message: "导出失败,请重试"
  484. });
  485. }
  486. };
  487. const handleDlgConfirm = () => {
  488. // 将导入的数据合并到dataList
  489. tableKey.value += 1;
  490. dlgImportExcelVisible.value = false;
  491. props.selProject.init(excelData.value);
  492. init();
  493. };
  494. const handleDlgClose = (done: () => void) => {
  495. ElMessageBox.confirm("确定要关闭这个对话框吗?")
  496. .then(() => {
  497. done();
  498. })
  499. .catch(() => {
  500. // catch error
  501. });
  502. };
  503. const onExcelDataProject = (data: Project) => {
  504. console.log("onExcelDataProject", data);
  505. excelData.value = data;
  506. };
  507. const init = () => {
  508. locationDataList.value = props.selProject.locations;
  509. personDataList.value = props.selProject.persons;
  510. tableKey.value = props.selProject.id;
  511. };
  512. onMounted(() => {
  513. init();
  514. });
  515. </script>