AdvancedForm.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template>
  2. <page-header-wrapper content="高级表单常见于一次性输入和提交大批量数据的场景">
  3. <a-card class="card" title="仓库管理" :bordered="false">
  4. <repository-form ref="repository" :showSubmit="false" />
  5. </a-card>
  6. <a-card class="card" title="任务管理" :bordered="false">
  7. <task-form ref="task" :showSubmit="false" />
  8. </a-card>
  9. <!-- table -->
  10. <a-card>
  11. <a-table
  12. :columns="columns"
  13. :dataSource="data"
  14. :pagination="false"
  15. :loading="memberLoading"
  16. >
  17. <template v-for="(col, i) in ['name', 'workId', 'department']" :slot="col" slot-scope="text, record">
  18. <a-input
  19. :key="col"
  20. v-if="record.editable"
  21. style="margin: -5px 0"
  22. :value="text"
  23. :placeholder="columns[i].title"
  24. @change="e => handleChange(e.target.value, record.key, col)"
  25. />
  26. <template v-else>{{ text }}</template>
  27. </template>
  28. <template slot="operation" slot-scope="text, record">
  29. <template v-if="record.editable">
  30. <span v-if="record.isNew">
  31. <a @click="saveRow(record)">添加</a>
  32. <a-divider type="vertical" />
  33. <a-popconfirm title="是否要删除此行?" @confirm="remove(record.key)">
  34. <a>删除</a>
  35. </a-popconfirm>
  36. </span>
  37. <span v-else>
  38. <a @click="saveRow(record)">保存</a>
  39. <a-divider type="vertical" />
  40. <a @click="cancel(record.key)">取消</a>
  41. </span>
  42. </template>
  43. <span v-else>
  44. <a @click="toggle(record.key)">编辑</a>
  45. <a-divider type="vertical" />
  46. <a-popconfirm title="是否要删除此行?" @confirm="remove(record.key)">
  47. <a>删除</a>
  48. </a-popconfirm>
  49. </span>
  50. </template>
  51. </a-table>
  52. <a-button style="width: 100%; margin-top: 16px; margin-bottom: 8px" type="dashed" icon="plus" @click="newMember">新增成员</a-button>
  53. </a-card>
  54. <!-- fixed footer toolbar -->
  55. <footer-tool-bar :is-mobile="isMobile" :collapsed="sideCollapsed">
  56. <span class="popover-wrapper">
  57. <a-popover title="表单校验信息" overlayClassName="antd-pro-pages-forms-style-errorPopover" trigger="click" :getPopupContainer="trigger => trigger.parentNode">
  58. <template slot="content">
  59. <li v-for="item in errors" :key="item.key" @click="scrollToField(item.key)" class="antd-pro-pages-forms-style-errorListItem">
  60. <a-icon type="cross-circle-o" class="antd-pro-pages-forms-style-errorIcon" />
  61. <div class="">{{ item.message }}</div>
  62. <div class="antd-pro-pages-forms-style-errorField">{{ item.fieldLabel }}</div>
  63. </li>
  64. </template>
  65. <span class="antd-pro-pages-forms-style-errorIcon" v-if="errors.length > 0">
  66. <a-icon type="exclamation-circle" />{{ errors.length }}
  67. </span>
  68. </a-popover>
  69. </span>
  70. <a-button type="primary" @click="validate" :loading="loading">提交</a-button>
  71. </footer-tool-bar>
  72. </page-header-wrapper>
  73. </template>
  74. <script>
  75. import RepositoryForm from './RepositoryForm'
  76. import TaskForm from './TaskForm'
  77. import FooterToolBar from '@/components/FooterToolbar'
  78. import { baseMixin } from '@/store/app-mixin'
  79. const fieldLabels = {
  80. name: '仓库名',
  81. url: '仓库域名',
  82. owner: '仓库管理员',
  83. approver: '审批人',
  84. dateRange: '生效日期',
  85. type: '仓库类型',
  86. name2: '任务名',
  87. url2: '任务描述',
  88. owner2: '执行人',
  89. approver2: '责任人',
  90. dateRange2: '生效日期',
  91. type2: '任务类型'
  92. }
  93. export default {
  94. name: 'AdvancedForm',
  95. mixins: [baseMixin],
  96. components: {
  97. FooterToolBar,
  98. RepositoryForm,
  99. TaskForm
  100. },
  101. data () {
  102. return {
  103. loading: false,
  104. memberLoading: false,
  105. // table
  106. columns: [
  107. {
  108. title: '成员姓名',
  109. dataIndex: 'name',
  110. key: 'name',
  111. width: '20%',
  112. scopedSlots: { customRender: 'name' }
  113. },
  114. {
  115. title: '工号',
  116. dataIndex: 'workId',
  117. key: 'workId',
  118. width: '20%',
  119. scopedSlots: { customRender: 'workId' }
  120. },
  121. {
  122. title: '所属部门',
  123. dataIndex: 'department',
  124. key: 'department',
  125. width: '40%',
  126. scopedSlots: { customRender: 'department' }
  127. },
  128. {
  129. title: '操作',
  130. key: 'action',
  131. scopedSlots: { customRender: 'operation' }
  132. }
  133. ],
  134. data: [
  135. {
  136. key: '1',
  137. name: '小明',
  138. workId: '001',
  139. editable: false,
  140. department: '行政部'
  141. },
  142. {
  143. key: '2',
  144. name: '李莉',
  145. workId: '002',
  146. editable: false,
  147. department: 'IT部'
  148. },
  149. {
  150. key: '3',
  151. name: '王小帅',
  152. workId: '003',
  153. editable: false,
  154. department: '财务部'
  155. }
  156. ],
  157. errors: []
  158. }
  159. },
  160. methods: {
  161. handleSubmit (e) {
  162. e.preventDefault()
  163. },
  164. newMember () {
  165. const length = this.data.length
  166. this.data.push({
  167. key: length === 0 ? '1' : (parseInt(this.data[length - 1].key) + 1).toString(),
  168. name: '',
  169. workId: '',
  170. department: '',
  171. editable: true,
  172. isNew: true
  173. })
  174. },
  175. remove (key) {
  176. const newData = this.data.filter(item => item.key !== key)
  177. this.data = newData
  178. },
  179. saveRow (record) {
  180. this.memberLoading = true
  181. const { key, name, workId, department } = record
  182. if (!name || !workId || !department) {
  183. this.memberLoading = false
  184. this.$message.error('请填写完整成员信息。')
  185. return
  186. }
  187. // 模拟网络请求、卡顿 800ms
  188. new Promise((resolve) => {
  189. setTimeout(() => {
  190. resolve({ loop: false })
  191. }, 800)
  192. }).then(() => {
  193. const target = this.data.find(item => item.key === key)
  194. target.editable = false
  195. target.isNew = false
  196. this.memberLoading = false
  197. })
  198. },
  199. toggle (key) {
  200. const target = this.data.find(item => item.key === key)
  201. target._originalData = { ...target }
  202. target.editable = !target.editable
  203. },
  204. getRowByKey (key, newData) {
  205. const data = this.data
  206. return (newData || data).find(item => item.key === key)
  207. },
  208. cancel (key) {
  209. const target = this.data.find(item => item.key === key)
  210. Object.keys(target).forEach(key => { target[key] = target._originalData[key] })
  211. target._originalData = undefined
  212. },
  213. handleChange (value, key, column) {
  214. const newData = [...this.data]
  215. const target = newData.find(item => key === item.key)
  216. if (target) {
  217. target[column] = value
  218. this.data = newData
  219. }
  220. },
  221. // 最终全页面提交
  222. validate () {
  223. const { $refs: { repository, task }, $notification } = this
  224. const repositoryForm = new Promise((resolve, reject) => {
  225. repository.form.validateFields((err, values) => {
  226. if (err) {
  227. reject(err)
  228. return
  229. }
  230. resolve(values)
  231. })
  232. })
  233. const taskForm = new Promise((resolve, reject) => {
  234. task.form.validateFields((err, values) => {
  235. if (err) {
  236. reject(err)
  237. return
  238. }
  239. resolve(values)
  240. })
  241. })
  242. // clean this.errors
  243. this.errors = []
  244. Promise.all([repositoryForm, taskForm]).then(values => {
  245. $notification['error']({
  246. message: 'Received values of form:',
  247. description: JSON.stringify(values)
  248. })
  249. }).catch(() => {
  250. const errors = Object.assign({}, repository.form.getFieldsError(), task.form.getFieldsError())
  251. const tmp = { ...errors }
  252. this.errorList(tmp)
  253. })
  254. },
  255. errorList (errors) {
  256. if (!errors || errors.length === 0) {
  257. return
  258. }
  259. this.errors = Object.keys(errors)
  260. .filter(key => errors[key])
  261. .map(key => ({
  262. key: key,
  263. message: errors[key][0],
  264. fieldLabel: fieldLabels[key]
  265. }))
  266. },
  267. scrollToField (fieldKey) {
  268. const labelNode = document.querySelector(`label[for="${fieldKey}"]`)
  269. if (labelNode) {
  270. labelNode.scrollIntoView(true)
  271. }
  272. }
  273. }
  274. }
  275. </script>
  276. <style lang="less" scoped>
  277. .card{
  278. margin-bottom: 24px;
  279. }
  280. .popover-wrapper {
  281. :deep(.antd-pro-pages-forms-style-errorPopover .ant-popover-inner-content) {
  282. min-width: 256px;
  283. max-height: 290px;
  284. padding: 0;
  285. overflow: auto;
  286. }
  287. }
  288. .antd-pro-pages-forms-style-errorIcon {
  289. user-select: none;
  290. margin-right: 24px;
  291. color: #f5222d;
  292. cursor: pointer;
  293. i {
  294. margin-right: 4px;
  295. }
  296. }
  297. .antd-pro-pages-forms-style-errorListItem {
  298. padding: 8px 16px;
  299. list-style: none;
  300. border-bottom: 1px solid #e8e8e8;
  301. cursor: pointer;
  302. transition: all .3s;
  303. &:hover {
  304. background: #e6f7ff;
  305. }
  306. .antd-pro-pages-forms-style-errorIcon {
  307. float: left;
  308. margin-top: 4px;
  309. margin-right: 12px;
  310. padding-bottom: 22px;
  311. color: #f5222d;
  312. }
  313. .antd-pro-pages-forms-style-errorField {
  314. margin-top: 2px;
  315. color: rgba(0,0,0,.45);
  316. font-size: 12px;
  317. }
  318. }
  319. </style>