edit-table.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. <!--
  2. * @Descripttion: 增删改查列表查询页-表格行编辑组件
  3. * @Author: Devli
  4. * @Date: 2021-7-17 10:42:24
  5. * @Last Modified by: qianlishi
  6. * @Last Modified time: 2021-12-13 11:04:24
  7. !-->
  8. <template>
  9. <div
  10. v-if="option['hide'] == null || option['hide'] == false"
  11. class="anji-card"
  12. >
  13. <div class="card-head">{{ option.title }}</div>
  14. <div class="card-body">
  15. <el-form ref="form" :model="form">
  16. <!-- 表格开始 -->
  17. <el-table
  18. :data="formRecordsUndelete"
  19. border
  20. :row-class-name="tableRowClassAdapter"
  21. @selection-change="handleSelectionChange"
  22. @row-click="handleTableRowClick"
  23. >
  24. <el-table-column label="序号" min-width="50" align="center">
  25. <template slot-scope="scope">
  26. {{ scope.$index + 1 }}
  27. </template>
  28. </el-table-column>
  29. <template v-for="item in option.columns">
  30. <el-table-column
  31. v-if="
  32. fieldIsHide(item.tableHide) != true &&
  33. item.columnType != 'expand'
  34. "
  35. :key="item.field"
  36. :label="item.label"
  37. :min-width="item.minWidth || 110"
  38. align="center"
  39. >
  40. <template slot-scope="scope">
  41. <el-form-item
  42. :prop="'records.' + scope.$index + '.' + item.field"
  43. :rules="item.rules"
  44. >
  45. <!-- 输入框 -->
  46. <el-input
  47. v-if="item.inputType == 'input'"
  48. v-model="scope.row[item.field]"
  49. size="small"
  50. :placeholder="item.placeholder || '请输入'"
  51. :clearable="item.clearable !== false"
  52. :disabled="
  53. saveButtonStatus[scope.$index] == 'inShow' ||
  54. item.disabled
  55. "
  56. @change="
  57. value => tableRowChange(scope.$index, item.field, value)
  58. "
  59. />
  60. <!-- 开关 -->
  61. <el-switch
  62. v-else-if="item.inputType == 'switch'"
  63. v-model="scope.row[item.field]"
  64. :disabled="
  65. saveButtonStatus[scope.$index] == 'inShow' ||
  66. item.disabled
  67. "
  68. :active-value="item.switchOption.disableValue"
  69. :inactive-value="item.switchOption.enableValue"
  70. @change="
  71. value => tableRowChange(scope.$index, item.field, value)
  72. "
  73. active-color="#5887fb"
  74. inactive-color="#ccc"
  75. >
  76. </el-switch>
  77. <el-input
  78. v-else-if="item.inputType == 'input-number'"
  79. v-model="scope.row[item.field]"
  80. size="small"
  81. :min="item.inputNumberOption.min"
  82. :max="item.inputNumberOption.max"
  83. :placeholder="item.placeholder || '请输入'"
  84. :clearable="item.clearable !== false"
  85. :disabled="
  86. saveButtonStatus[scope.$index] == 'inShow' ||
  87. item.disabled
  88. "
  89. @change="
  90. value => tableRowChange(scope.$index, item.field, value)
  91. "
  92. />
  93. <!-- 自定义input -->
  94. <anji-input
  95. v-else-if="item.inputType == 'anji-input'"
  96. v-model.trim="scope.row[item.field]"
  97. :default-value="item.defaultValue"
  98. :unit="item.anjiInput.unit"
  99. :conversion="item.anjiInput.conversion"
  100. :placeholder="item.placeholder || '请输入'"
  101. :clearable="item.clearable !== false"
  102. :disabled="
  103. saveButtonStatus[scope.$index] == 'inShow' ||
  104. item.disabled
  105. "
  106. @change="
  107. value => tableRowChange(scope.$index, item.field, value)
  108. "
  109. />
  110. <!-- 下拉框 -->
  111. <anji-select
  112. v-else-if="item.inputType == 'anji-select'"
  113. v-model.trim="scope.row[item.field]"
  114. :multiple="item.anjiSelectOption.multiple"
  115. :default-value="item.defaultValue"
  116. :dict-code="item.anjiSelectOption.dictCode"
  117. :url="item.anjiSelectOption.url"
  118. :method="item.anjiSelectOption.method"
  119. :query-param="item.anjiSelectOption.queryParam"
  120. :option="item.anjiSelectOption.option"
  121. :label="item.anjiSelectOption.label"
  122. :disabled-options="item.anjiSelectOption.disabledOptions"
  123. :disabled="
  124. saveButtonStatus[scope.$index] == 'inShow' ||
  125. item.disabled
  126. "
  127. @change="
  128. (value, option) =>
  129. tableRowChange(scope.$index, item.field, value, option)
  130. "
  131. />
  132. <!-- 日期时间框 -->
  133. <el-date-picker
  134. v-else-if="item.inputType.indexOf('date') >= 0"
  135. v-model="scope.row[item.field]"
  136. style="width: 100%"
  137. :placeholder="item.placeholder || '请选择'"
  138. :type="item.inputType"
  139. :clearable="item.clearable !== false"
  140. :disabled="
  141. saveButtonStatus[scope.$index] == 'inShow' ||
  142. item.disabled
  143. "
  144. @change="
  145. value => tableRowChange(scope.$index, item.field, value)
  146. "
  147. />
  148. <!-- 待扩展的表单类型,请自行扩展 -->
  149. <el-input
  150. v-else
  151. placeholder="组件不支持此类型表单请至组件内部自行扩展"
  152. disabled
  153. />
  154. </el-form-item>
  155. </template>
  156. </el-table-column>
  157. </template>
  158. <el-table-column fixed="right" label="操作" width="100">
  159. <template slot-scope="scope">
  160. <el-button
  161. type="text"
  162. size="small"
  163. @click="handleAddOrUpdate(scope.row, scope.$index)"
  164. >{{ getRowEditButton(scope.$index) }}</el-button
  165. >
  166. <el-button
  167. type="text"
  168. size="small"
  169. @click="handleDelete(scope.row, scope.$index)"
  170. >删除</el-button
  171. >
  172. </template>
  173. </el-table-column>
  174. </el-table>
  175. <!-- 表格结束 -->
  176. </el-form>
  177. <button
  178. v-if="modelType != 'view'"
  179. class="table-add-row-button"
  180. @click="handleAdd"
  181. >
  182. <i class="el-icon-plus" />
  183. <span>新增</span>
  184. </button>
  185. </div>
  186. </div>
  187. </template>
  188. <script>
  189. const ROW_DELETE_FLAG = "deletedFlag";
  190. export default {
  191. components: {},
  192. props: {
  193. modelType: String, // add view edit
  194. option: {
  195. // 界面渲染相关配置json
  196. type: [Object],
  197. default: () => {
  198. return {
  199. title: "", // 页面标题
  200. labelWidth: "",
  201. queryFormFields: [], // 查询表单条件
  202. buttons: {
  203. // 按钮
  204. query: {},
  205. edit: {},
  206. delete: {},
  207. add: {}
  208. },
  209. columns: [] // 表格列
  210. };
  211. }
  212. },
  213. relateData: {
  214. // 关联的主记录
  215. type: [Object],
  216. default: () => {
  217. return {};
  218. }
  219. },
  220. value: {
  221. type: [Array],
  222. default: () => {
  223. return [];
  224. }
  225. },
  226. valueNew: {
  227. type: [Array],
  228. default: () => {
  229. return [];
  230. }
  231. }
  232. },
  233. data() {
  234. return {
  235. checkRecords: [], // 表格中当前选中的记录
  236. form: {
  237. records: [], // 接口返回的记录列表
  238. total: 0 // 接口返回的总条数
  239. },
  240. saveButtonStatus: [], // 维护表格中每行编辑按钮的状态 inShow inEditing inAdding
  241. rowIdList: []
  242. };
  243. },
  244. computed: {
  245. // 主键的列名
  246. primaryKeyFieldName() {
  247. let primaryKey = this.option.columns.find(
  248. item => item["primaryKey"] == true
  249. );
  250. if (primaryKey != null) {
  251. return primaryKey["field"];
  252. } else {
  253. return null;
  254. console.warn(
  255. "在columns中查找primaryKey=true失败,会导致查询详情和删除失败"
  256. );
  257. }
  258. },
  259. // 指定当前实体关联主表的关联字段,孙子关联表,没有该属性
  260. joinColumn() {
  261. let columnName = this.option.joinColumn;
  262. if (this.isBlank(columnName)) {
  263. console.warn(
  264. "在columns中查找关联字段失败,会导致查询详情和删除失败,孙子关联表忽略该错误"
  265. );
  266. columnName = "";
  267. }
  268. return columnName;
  269. },
  270. // 未删除的记录
  271. formRecordsUndelete() {
  272. if (this.form.records == null) {
  273. return [];
  274. }
  275. return this.form.records.filter(
  276. item => item[ROW_DELETE_FLAG] == null || item[ROW_DELETE_FLAG] == false
  277. );
  278. }
  279. },
  280. watch: {},
  281. created() {
  282. // 主表 relateData 的关联字段 joinColumn 变动时,触发查询子表的查询,孙子关联儿子的无效
  283. if (this.isNotBlank(this.joinColumn)) {
  284. this.$watch(
  285. function() {
  286. return this.relateData[this.joinColumn];
  287. },
  288. function(newVal, oldVal) {
  289. // 如果是关联字段发生更新,触发查询
  290. if (this.isNotBlank(newVal)) {
  291. this.handleQueryPageList(newVal);
  292. } else {
  293. // 如果关联字段为空,清空本表格的数据,如果是父组件(弹出框)关闭时,设置this.relateData = {默认值}时触发
  294. this.checkRecords = [];
  295. this.form.records = [];
  296. this.form.total = 0;
  297. this.saveButtonStatus = [];
  298. }
  299. }
  300. );
  301. }
  302. },
  303. mounted() {
  304. // 首次打开时,根据主表关联字段查询子表,加载表格数据
  305. if (
  306. this.isNotBlank(this.relateData) &&
  307. this.isNotBlank(this.relateData[this.joinColumn])
  308. ) {
  309. this.handleQueryPageList();
  310. }
  311. },
  312. methods: {
  313. // 该行是否显示 true/false/ 'hideOnAdd hideOnView hideOnEdit'
  314. fieldIsHide(tableHide) {
  315. if (typeof tableHide == "boolean") {
  316. return tableHide;
  317. }
  318. if (typeof tableHide == "string") {
  319. if (this.modelType == "add") {
  320. return tableHide.indexOf("hideOnAdd") >= 0;
  321. }
  322. if (this.modelType == "view") {
  323. return tableHide.indexOf("hideOnView") >= 0;
  324. }
  325. if (this.modelType == "edit") {
  326. return tableHide.indexOf("hideOnEdit") >= 0;
  327. }
  328. }
  329. return false;
  330. },
  331. // 获取行的提交按钮文字
  332. getRowEditButton(index) {
  333. if (this.saveButtonStatus[index] == "inEditing") {
  334. return "btn_savetemp";
  335. } else if (this.saveButtonStatus[index] == "inAdding") {
  336. return "btn_savetemp";
  337. } else if (this.saveButtonStatus[index] == "inShow") {
  338. return "btn_edit";
  339. } else {
  340. return "not_permission";
  341. }
  342. },
  343. // 表格行渲染前前置处理
  344. tableRowClassAdapter({ row, rowIndex }) {
  345. row.index = rowIndex;
  346. },
  347. // 查询
  348. async handleQueryPageList(joinColumnValue) {
  349. if (this.isBlank(joinColumnValue)) {
  350. joinColumnValue = this.relateData[this.joinColumn];
  351. }
  352. let params = {};
  353. params[this.joinColumn] = joinColumnValue;
  354. this.queryPageList(params);
  355. },
  356. // 暴露给外部直接调用带参数
  357. async queryPageList(params) {
  358. // 默认的排序
  359. if (this.isNotBlank(this.option.buttons.query.order)) {
  360. params["sort"] = this.option.buttons.query.sort;
  361. params["order"] = this.option.buttons.query.order;
  362. }
  363. const { data, code } = await this.option.buttons.query.api(params);
  364. if (code != "200") return;
  365. this.form.records = data.records;
  366. this.form.total = data.total;
  367. this.$emit("input", this.form.records);
  368. for (let i = 0; i < this.form.total; i++) {
  369. this.saveButtonStatus.push("inShow");
  370. }
  371. },
  372. // 选择项改变时
  373. handleSelectionChange(val) {
  374. this.checkRecords = val;
  375. },
  376. // 表格选中某一行时
  377. handleTableRowClick(row, column, event) {
  378. // 行点击后,回调option中的tableRowClick事件
  379. if (typeof this.option.tableRowClick == "function") {
  380. this.option.tableRowClick(
  381. this.form.records,
  382. row,
  383. row.index,
  384. this.relateData
  385. );
  386. }
  387. },
  388. // 行数据更新时回调
  389. tableRowChange(rowIndex, fieldName, fieldVal, fieldExtend) {
  390. // 通知外面的组件
  391. this.$emit("input", this.form.records);
  392. // 表单变动后,回调option中的tableRowChange事件
  393. if (typeof this.option.tableChange == "function") {
  394. this.option.tableChange(
  395. this.form.records,
  396. rowIndex,
  397. fieldName,
  398. fieldVal,
  399. fieldExtend,
  400. this.relateData
  401. );
  402. }
  403. },
  404. // 新增
  405. handleAdd() {
  406. this.saveButtonStatus.push("inAdding");
  407. this.form.records.push({});
  408. },
  409. // 父节点Change,子节点表格更新
  410. handleUpdata() {
  411. this.saveButtonStatus = [];
  412. this.form.records = [];
  413. },
  414. // 提交和修改
  415. handleAddOrUpdate(row, index) {
  416. // 编辑状态下点击保存提交
  417. if (
  418. this.saveButtonStatus[index] == "inEditing" ||
  419. this.saveButtonStatus[index] == "inAdding"
  420. ) {
  421. this.handleSaveTemp(row, index);
  422. } else {
  423. this.$set(this.saveButtonStatus, index, "inEditing");
  424. }
  425. },
  426. // 校验表单
  427. validate(callback) {
  428. this.$refs["form"].validate(async (valid, obj) => {
  429. if (callback != null) {
  430. callback(valid);
  431. }
  432. });
  433. },
  434. // 暂存
  435. async handleSaveTemp(row, index) {
  436. this.$refs["form"].validate(valid => {
  437. if (valid) {
  438. if (this.isBlank(row[this.primaryKeyFieldName])) {
  439. // 补全关联属性
  440. if (typeof this.option.beforeInsert == "function") {
  441. this.option.beforeInsert(this.relateData, row);
  442. }
  443. } else {
  444. // 补全关联属性
  445. if (typeof this.option.beforeUpdate == "function") {
  446. this.option.beforeUpdate(this.relateData, row);
  447. }
  448. }
  449. // 将行按钮的文字改成编辑
  450. this.$set(this.saveButtonStatus, index, "inShow");
  451. // 通知外面的组件
  452. this.$emit("input", this.form.records);
  453. }
  454. });
  455. },
  456. // 删除
  457. handleDelete(row, index) {
  458. this.saveButtonStatus.splice(index, 1); // 清空状态
  459. // 界面上临时新增出来的一行,还没有提交到数据库,可以直接删除
  460. if (this.saveButtonStatus[index] == "inAdding") {
  461. this.form.records.splice(index, 1);
  462. this.saveButtonStatus.splice(index, 1);
  463. this.$emit("input", this.form.records);
  464. return;
  465. }
  466. // if (this.isBlank(row) || this.isBlank(row[this.primaryKeyFieldName])) {
  467. // return
  468. // }
  469. // 将对应的行标识成删除
  470. // 找出该行在原始记录中的index
  471. let realIndex = this.form.records.findIndex(
  472. item => item[this.primaryKeyFieldName] == row[this.primaryKeyFieldName]
  473. );
  474. row[ROW_DELETE_FLAG] = true;
  475. this.$set(this.form.records, realIndex, row);
  476. this.$emit("input", this.form.records);
  477. }
  478. }
  479. };
  480. </script>
  481. <style scoped lang="scss">
  482. .table-add-row-button {
  483. width: 100%;
  484. margin-top: 0px;
  485. margin-bottom: 0px;
  486. border-color: #d9d9d9;
  487. border-style: dashed;
  488. line-height: 1.499;
  489. position: relative;
  490. display: inline-block;
  491. font-weight: 400;
  492. white-space: nowrap;
  493. text-align: center;
  494. background-image: none;
  495. border: 1px solid transparent;
  496. box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
  497. cursor: pointer;
  498. transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  499. -webkit-user-select: none;
  500. -ms-user-select: none;
  501. user-select: none;
  502. -ms-touch-action: manipulation;
  503. touch-action: manipulation;
  504. height: 32px;
  505. padding: 0 15px;
  506. font-size: 14px;
  507. border-radius: 4px;
  508. color: rgba(0, 0, 0, 0.65);
  509. background-color: #fff;
  510. border-color: #d9d9d9;
  511. }
  512. </style>