edit.vue 14 KB

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