Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 

594 строки
26 KiB

  1. <template>
  2. <div>
  3. <v-dialog
  4. v-model="progress"
  5. persistent
  6. width="300"
  7. >
  8. <v-card
  9. color="primary"
  10. dark
  11. >
  12. <v-card-text>
  13. 載入中...
  14. <v-progress-linear
  15. indeterminate
  16. color="white"
  17. class="mb-0"
  18. ></v-progress-linear>
  19. </v-card-text>
  20. </v-card>
  21. </v-dialog>
  22. <v-data-table class="" :headers="headers" :items="items"
  23. disable-sort
  24. fixed-header
  25. :footer-props="{'items-per-page-options': [30, 40, 50, 60]}"
  26. :items-per-page="30">
  27. <template v-slot:top>
  28. <v-toolbar flat>
  29. <v-toolbar-title class="title font-weight-bold">{{title}}</v-toolbar-title>
  30. <v-divider
  31. class="mx-4"
  32. inset
  33. vertical
  34. />
  35. <div class="row align-center">
  36. <p v-if="haveSearch" class="mb-0 pa-0">搜尋條件:</p>
  37. <div v-for="(val, key, index) in searchItem" :key="index" class="pa-0 ma-0">
  38. <v-chip color="secondary" v-if="val !== null">
  39. {{mappingHeaders[key]}} 包含: {{val}}
  40. <v-icon @click="clearSinglePropInSearchItem(key)">mdi-close</v-icon>
  41. </v-chip>
  42. </div>
  43. </div>
  44. <v-spacer/>
  45. <!--匯入檔案對話-->
  46. <v-dialog
  47. v-model="dialogImport"
  48. @click:outside="close"
  49. :retain-focus="false"
  50. max-width="500"
  51. >
  52. <template v-slot:activator="{ on, attrs }">
  53. <v-btn
  54. color="white"
  55. class="primary mr-5"
  56. v-bind="attrs"
  57. v-on="on"
  58. icon
  59. >
  60. <v-icon>mdi-file-import-outline</v-icon>
  61. </v-btn>
  62. </template>
  63. <v-card>
  64. <v-card-title>
  65. <span class="headline font-weight-bold">匯入</span>
  66. </v-card-title>
  67. <v-card-text>
  68. <v-container>
  69. <div class="red--text text--lighten-3 d-flex">1.請確認<p class="font-weight-bold red--text text--lighten-1 mb-0">匯入之類別</p>相符</div>
  70. <div class="red--text text--lighten-3 d-flex">1.請確認<p class="font-weight-bold red--text text--lighten-1 mb-0">欄位名稱</p>一致</div>
  71. <div class="red--text text--lighten-3 d-flex">3.<p class="font-weight-bold red--text text--lighten-1">欄位順序</p>不影響匯入</div>
  72. <input id="file" type="file" @change="onFileChange" />
  73. </v-container>
  74. </v-card-text>
  75. <v-card-actions>
  76. <v-spacer></v-spacer>
  77. <v-btn
  78. color="blue darken-1"
  79. text
  80. @click="close"
  81. >
  82. 關閉
  83. </v-btn>
  84. <v-btn
  85. color="blue darken-1"
  86. text
  87. @click="importFile"
  88. >
  89. 匯入
  90. </v-btn>
  91. </v-card-actions>
  92. </v-card>
  93. </v-dialog>
  94. <v-btn
  95. color="white"
  96. class="primary mr-5"
  97. @click="exportFile"
  98. icon
  99. >
  100. <v-icon>mdi-file-export-outline</v-icon>
  101. </v-btn>
  102. <!--搜尋對話-->
  103. <v-dialog
  104. v-model="dialogSearch"
  105. @click:outside="close"
  106. :retain-focus="false"
  107. >
  108. <template v-slot:activator="{ on, attrs }">
  109. <v-btn
  110. color="white"
  111. class="primary mr-5"
  112. v-bind="attrs"
  113. v-on="on"
  114. icon
  115. >
  116. <v-icon>mdi-magnify</v-icon>
  117. </v-btn>
  118. </template>
  119. <v-card>
  120. <v-card-title>
  121. <span class="headline font-weight-bold">搜尋</span>
  122. </v-card-title>
  123. <v-card-text>
  124. <v-container>
  125. <v-row>
  126. <v-col
  127. v-for="(header, index) in headers"
  128. :key="index"
  129. cols="12"
  130. sm="6"
  131. md="4"
  132. >
  133. <div v-if="headers[index] && headers[index].text !== '編號'">
  134. <v-text-field
  135. dense
  136. v-if="isTextField(header.value) && headers[index].text"
  137. :label="header.text"
  138. :disabled="isDisabled(header.value)"
  139. v-model="searchItem[header.value]"
  140. :persistent-hint="isRequire(header.value)"
  141. hint="必填"
  142. />
  143. <v-select
  144. dense
  145. v-if="isSelect(header.value) && headers[index].text"
  146. :persistent-hint="isRequire(header.value)"
  147. hint="必選"
  148. :label="header.text"
  149. v-model="searchItem[header.value]"
  150. :items="selectItem[header.value]"
  151. item-text="item"
  152. item-value="item"
  153. />
  154. </div>
  155. </v-col>
  156. </v-row>
  157. </v-container>
  158. </v-card-text>
  159. <v-card-actions>
  160. <v-spacer></v-spacer>
  161. <v-btn
  162. color="blue darken-1"
  163. text
  164. @click="clearSearchItem"
  165. >
  166. 清空
  167. </v-btn>
  168. <v-btn
  169. color="blue darken-1"
  170. text
  171. @click="close"
  172. >
  173. 關閉
  174. </v-btn>
  175. <v-btn
  176. color="blue darken-1"
  177. text
  178. @click="search"
  179. >
  180. 搜尋
  181. </v-btn>
  182. </v-card-actions>
  183. </v-card>
  184. </v-dialog>
  185. <!--新增對話-->
  186. <v-dialog
  187. v-model="dialogInsert"
  188. @click:outside="close"
  189. >
  190. <template v-slot:activator="{ on, attrs }">
  191. <v-btn
  192. color="white"
  193. class="primary"
  194. v-bind="attrs"
  195. v-on="on"
  196. icon
  197. >
  198. <v-icon>mdi-plus</v-icon>
  199. </v-btn>
  200. </template>
  201. <v-card>
  202. <v-card-title>
  203. <span class="headline font-weight-bold">新增</span>
  204. </v-card-title>
  205. <v-card-text>
  206. <v-container>
  207. <v-row>
  208. <v-col
  209. v-for="(header, index) in headers"
  210. :key="index"
  211. cols="12"
  212. sm="6"
  213. md="4"
  214. >
  215. <div v-if="headers[index] && headers[index].text !== '編號'">
  216. <v-text-field
  217. dense
  218. v-if="isTextField(header.value) && headers[index].text"
  219. :label="header.text"
  220. :disabled="isDisabled(header.value)"
  221. v-bind:value="insertItem[header.value]"
  222. :persistent-hint="isRequire(header.value)"
  223. hint="必填"
  224. v-on:input="oninput(insertItem, header.value, $event)"
  225. />
  226. <v-select
  227. dense
  228. v-if="isSelect(header.value) && headers[index].text"
  229. :persistent-hint="isRequire(header.value)"
  230. hint="必選"
  231. :label="header.text"
  232. :value="insertItem[header.value]"
  233. v-on:input="oninput(insertItem, header.value, $event)"
  234. :items="selectItem[header.value]"
  235. item-text="item"
  236. item-value="item"
  237. />
  238. </div>
  239. </v-col>
  240. </v-row>
  241. </v-container>
  242. </v-card-text>
  243. <v-card-actions>
  244. <v-spacer></v-spacer>
  245. <v-btn
  246. color="blue darken-1"
  247. text
  248. @click="close"
  249. >
  250. 取消
  251. </v-btn>
  252. <v-btn
  253. color="blue darken-1"
  254. text
  255. @click="insertOne"
  256. >
  257. 新增
  258. </v-btn>
  259. </v-card-actions>
  260. </v-card>
  261. </v-dialog>
  262. </v-toolbar>
  263. </template>
  264. <template v-slot:item.actions="{ item }">
  265. <div class="d-flex">
  266. <v-btn
  267. icon
  268. @click="openDialogModify(item)"
  269. >
  270. <v-icon>mdi-pencil</v-icon>
  271. </v-btn>
  272. <v-icon
  273. small
  274. @click="deleteOne(item)"
  275. >
  276. mdi-delete
  277. </v-icon>
  278. </div>
  279. </template>
  280. </v-data-table>
  281. <!--修改對話-->
  282. <v-dialog
  283. v-model="dialogModify"
  284. @click:outside="close"
  285. :retain-focus="false"
  286. >
  287. <v-card>
  288. <v-card-title>
  289. <span class="headline font-weight-bold">修改</span>
  290. </v-card-title>
  291. <v-card-text>
  292. <v-container>
  293. <v-row>
  294. <v-col
  295. v-for="(header, index) in headers"
  296. :key="index"
  297. cols="12"
  298. sm="6"
  299. md="4"
  300. >
  301. <div v-if="headers[index] && headers[index].text !== '編號'">
  302. <v-text-field
  303. dense
  304. v-if="isTextField(header.value) && headers[index].text"
  305. :label="header.text"
  306. :disabled="isDisabled(header.value)"
  307. v-bind:value="modifyItem[header.value]"
  308. :persistent-hint="isRequire(header.value)"
  309. hint="必填"
  310. v-on:input="oninput(modifyItem, header.value, $event)"
  311. />
  312. <v-select
  313. dense
  314. v-if="isSelect(header.value) && headers[index].text"
  315. :persistent-hint="isRequire(header.value)"
  316. hint="必選"
  317. :label="header.text"
  318. :value="modifyItem[header.value]"
  319. v-on:input="oninput(modifyItem, header.value, $event)"
  320. :items="selectItem[header.value]"
  321. item-text="item"
  322. item-value="item"
  323. />
  324. </div>
  325. </v-col>
  326. </v-row>
  327. </v-container>
  328. </v-card-text>
  329. <v-card-actions>
  330. <v-spacer></v-spacer>
  331. <v-btn
  332. color="blue darken-1"
  333. text
  334. @click="close"
  335. >
  336. 取消
  337. </v-btn>
  338. <v-btn
  339. color="blue darken-1"
  340. text
  341. @click="modifyOne(item)"
  342. >
  343. 修改
  344. </v-btn>
  345. </v-card-actions>
  346. </v-card>
  347. </v-dialog>
  348. </div>
  349. </template>
  350. <script>
  351. export default {
  352. name: '',
  353. data() {
  354. return {
  355. fullHeight: 0,
  356. dialogInsert: false,
  357. dialogModify: false,
  358. dialogSearch: false,
  359. title: '',
  360. headers: [],
  361. mappingHeaders: {},
  362. cols: {},
  363. items: [],
  364. isselect: [],
  365. isrequired: [],
  366. insertItem: {},
  367. modifyItem: {},
  368. selectItem: {},
  369. searchItem: {},
  370. mockSearch: false,
  371. haveSearch:false,
  372. dialogImport: false,
  373. file: null,
  374. progress: false,
  375. }
  376. },
  377. async mounted() {
  378. this.progress = true;
  379. this.getHeaders();
  380. this.getInventories();
  381. this.getTitle();
  382. this.getSelectItem();
  383. this.fullHeight = window.innerHeight;
  384. window.onresize = () => {
  385. this.fullHeight = window.innerHeight;
  386. };
  387. },
  388. computed: {
  389. tablename() {
  390. return this.$route.params.tablename;
  391. },
  392. },
  393. watch: {
  394. async tablename() {
  395. if (this.$route.params.tablename) {
  396. this.haveSearch = false;
  397. this.progress = true;
  398. this.title = '';
  399. await this.getHeaders();
  400. await this.getInventories();
  401. await this.getTitle();
  402. await this.getSelectItem();
  403. }
  404. },
  405. },
  406. methods: {
  407. log() {
  408. alert('as');
  409. },
  410. openDialogModify(item) {
  411. this.modifyItem = JSON.parse(JSON.stringify(item));
  412. this.dialogModify = true;
  413. },
  414. oninput(item, key, val) {
  415. this.$set(item, key, val);
  416. },
  417. async getTitle() {
  418. await this.$axios.get(`/title?tablename=${this.tablename}`).then((resp) => {
  419. this.title = `${resp.data.data}類`;
  420. });
  421. },
  422. async getHeaders() {
  423. await this.$axios.get(`/headers?tablename=${this.tablename}`)
  424. .then((resp) => {
  425. this.headers = [];
  426. this.headers.push({'text': '', 'value': 'actions', sortable: false});
  427. this.insertItem = {};
  428. this.searchItem = {};
  429. this.cols = resp.data.data;
  430. resp.data.data.forEach((item) => {
  431. let header = {'text': null, 'value': null};
  432. header.text = item.descript;
  433. header.value = item.colname;
  434. this.mappingHeaders[item.colname] = item.descript;
  435. this.headers.push(header);
  436. if (item.isselect === 'true') {
  437. this.isselect.push(item.colname);
  438. }
  439. if (item.isrequire === 'true') {
  440. this.isrequired.push(item.colname);
  441. }
  442. this.insertItem[item.colname] = null;
  443. this.searchItem[item.colname] = null;
  444. });
  445. });
  446. },
  447. async getInventories() {
  448. await this.$axios.get(`/inventory?tablename=${this.tablename}`)
  449. .then((resp) => {
  450. this.items = [];
  451. this.items = resp.data.data;
  452. this.progress = false;
  453. });
  454. },
  455. async getSelectItem() {
  456. await this.$axios.get(`/selectItem?tablename=${this.tablename}`).then((resp) => {
  457. this.selectItem = {};
  458. this.selectItem = resp.data.data;
  459. });
  460. },
  461. insertOne() {
  462. this.insertItem.tablename = this.tablename;
  463. this.$axios.post(`/inventory?`, this.insertItem).then(() => {
  464. delete this.insertItem.tablename;
  465. this.getInventories();
  466. this.close();
  467. alert('已新增');
  468. }
  469. );
  470. },
  471. close() {
  472. this.dialogInsert = false;
  473. this.dialogModify = false;
  474. this.dialogSearch = false;
  475. this.dialogImport = false;
  476. },
  477. isDisabled(key) {
  478. const disabledKey = ['id'];
  479. return disabledKey.indexOf(key) >= 0;
  480. },
  481. isTextField(key) {
  482. return this.isselect.indexOf(key) < 0;
  483. },
  484. isSelect(key) {
  485. return this.isselect.indexOf(key) >= 0;
  486. },
  487. isRequire(key) {
  488. return this.isrequired.indexOf(key) >= 0;
  489. },
  490. modifyOne(item) {
  491. item.tablename = this.tablename;
  492. this.$axios.put(`/inventory?`, item).then(() => {
  493. this.getInventories();
  494. this.close();
  495. }
  496. );
  497. },
  498. deleteOne(item) {
  499. let yes = confirm('確定刪除');
  500. if (yes) {
  501. this.$axios.delete(`/deleteOne?tablename=${this.tablename}&&id=${item.id}`).then(() => {
  502. if (this.haveSearch === true) {
  503. this.search();
  504. } else {
  505. this.getInventories();
  506. }
  507. alert('已刪除');
  508. });
  509. }
  510. },
  511. search() {
  512. this.searchItem.tablename = this.tablename;
  513. this.$axios.post(`/search`, this.searchItem).then((resp) => {
  514. delete this.searchItem.tablename;
  515. this.items = resp.data.data;
  516. this.haveSearch = true;
  517. this.dialogSearch = false;
  518. });
  519. },
  520. clearSinglePropInSearchItem(key) {
  521. const self = this;
  522. this.searchItem[key] = null;
  523. this.haveSearch = false;
  524. Object.keys(this.searchItem).forEach(function(key){
  525. if (self.searchItem[key] && self.searchItem[key] !== null) {
  526. this.haveSearch = true;
  527. }
  528. });
  529. this.getInventories();
  530. },
  531. async clearSearchItem() {
  532. const self = this;
  533. Object.keys(this.searchItem).forEach(function(key){
  534. self.searchItem[key] = null;
  535. });
  536. this.getInventories();
  537. this.haveSearch = false;
  538. },
  539. onFileChange(e) {
  540. const files = e.target.files || e.dataTransfer.files;
  541. if (files.length > 0) {
  542. console.log(files[0].name);
  543. // eslint-disable-next-line prefer-destructuring
  544. this.file = files[0];
  545. }
  546. },
  547. importFile() {
  548. this.progress = true;
  549. const formData = new FormData();
  550. formData.append('tablename', this.tablename);
  551. formData.append('file', this.file);
  552. this.$axios.post(`/importFile`, formData).then(() => {
  553. document.getElementById('file').value = null;
  554. this.getInventories();
  555. this.file = null;
  556. this.progress = false;
  557. this.dialogImport = false;
  558. });
  559. },
  560. exportFile() {
  561. let yes = confirm('確定匯出');
  562. if (yes) {
  563. this.$axios.post(`/exportFile`, {items: this.items, info: {tablename: this.tablename}}, {responseType: 'blob'})
  564. .then((resp) => {
  565. console.log(resp.headers['filename']);
  566. const blob = new Blob([resp.data], {type: "application/vnd.ms-excel;charset=utf-8"});
  567. const reader = new FileReader();
  568. reader.readAsDataURL(blob);
  569. reader.onload = (e) => {
  570. const a = document.createElement('a');
  571. a.download = resp.headers['filename'];
  572. a.href = e.target.result;
  573. document.body.appendChild(a);
  574. a.click();
  575. document.body.removeChild(a);
  576. };
  577. }
  578. );
  579. }
  580. }
  581. }
  582. }
  583. </script>