You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

621 lines
22 KiB

2 months ago
2 weeks ago
2 months ago
2 weeks ago
4 weeks ago
2 months ago
2 weeks ago
2 months ago
2 weeks ago
2 months ago
4 weeks ago
2 months ago
2 weeks ago
2 months ago
2 weeks ago
2 months ago
4 weeks ago
2 months ago
  1. <template>
  2. <div class="customer-css">
  3. <!-- 查询条件 -->
  4. <el-form :inline="true" label-position="top" class="pi-search-form">
  5. <el-form-item label="字典名称">
  6. <el-input v-model="typeSearch.dictName" clearable placeholder="字典名称" style="width:160px" @keyup.enter.native="loadTypeList" />
  7. </el-form-item>
  8. <el-form-item label="字典类型">
  9. <el-input v-model="typeSearch.dictType" clearable placeholder="字典类型" style="width:200px" @keyup.enter.native="loadTypeList" />
  10. </el-form-item>
  11. <el-form-item label="状态">
  12. <el-select v-model="typeSearch.status" clearable placeholder="请选择" style="width:100px">
  13. <el-option label="全部" value="" />
  14. <el-option label="启用" value="Y" />
  15. <el-option label="停用" value="N" />
  16. </el-select>
  17. </el-form-item>
  18. <el-form-item label=" ">
  19. <el-button type="primary" class="customer-bun-min" @click="loadTypeList">查询</el-button>
  20. </el-form-item>
  21. </el-form>
  22. <!-- 主表字典类型系统配置只读 -->
  23. <el-table
  24. ref="typeTable"
  25. :data="typeList"
  26. :height="tableHeight"
  27. border
  28. highlight-current-row
  29. v-loading="typeLoading"
  30. style="width:100%"
  31. @row-click="selectDictType">
  32. <!-- <el-table-column prop="dictType" label="字典类型" min-width="120" header-align="center" align="left" show-overflow-tooltip />-->
  33. <el-table-column prop="dictName" label="字典名称" min-width="120" header-align="center" align="left" show-overflow-tooltip />
  34. <el-table-column prop="status" label="状态" width="80" header-align="center" align="center">
  35. <template slot-scope="scope">
  36. {{ scope.row.status === 'Y' ? '启用' : '停用' }}
  37. </template>
  38. </el-table-column>
  39. <el-table-column label="操作" width="70" fixed="right" header-align="center" align="center">
  40. <template slot-scope="scope">
  41. <el-link
  42. v-if="canDeleteType(scope.row)"
  43. style="cursor:pointer"
  44. @click.stop="handleDeleteType(scope.row)">删除</el-link>
  45. <span v-else>-</span>
  46. </template>
  47. </el-table-column>
  48. <el-table-column prop="remark" label="备注" min-width="300" header-align="center" align="left" show-overflow-tooltip />
  49. <!-- <el-table-column prop="createBy" label="创建人" width="100" header-align="center" align="center" />-->
  50. <!-- <el-table-column prop="createDate" label="创建时间" min-width="140" header-align="center" align="center" />-->
  51. </el-table>
  52. <el-pagination
  53. style="margin-top:5px"
  54. @size-change="typeSizeChange"
  55. @current-change="typePageChange"
  56. :current-page="typePageIndex"
  57. :page-sizes="[20, 50, 100]"
  58. :page-size="typePageSize"
  59. :total="typeTotal"
  60. layout="total, sizes, prev, pager, next, jumper" />
  61. <!-- 子表字典明细 -->
  62. <div class="sub-title">
  63. 字典明细列表
  64. <el-button
  65. type="primary"
  66. class="customer-bun-min"
  67. style="margin-left:10px"
  68. :disabled="!currentType.dictType"
  69. @click="openDataDialog()">
  70. 新增明细
  71. </el-button>
  72. </div>
  73. <el-form :inline="true" label-position="top" class="pi-search-form" style="margin-top:0">
  74. <el-form-item label="字典标签">
  75. <el-input v-model="dataSearch.dictLabel" clearable placeholder="标签" style="width:160px" @keyup.enter.native="loadDataList" />
  76. </el-form-item>
  77. <el-form-item label="作用范围">
  78. <el-select v-model="dataSearch.scope" clearable placeholder="请选择" style="width:120px" @change="applyDataFilterAndPage">
  79. <el-option label="全部" value="" />
  80. <el-option label="所有工厂" value="all" />
  81. <el-option label="当前工厂" value="current" />
  82. </el-select>
  83. </el-form-item>
  84. <el-form-item label="状态">
  85. <el-select v-model="dataSearch.status" clearable placeholder="请选择" style="width:100px">
  86. <el-option label="全部" value="" />
  87. <el-option label="启用" value="Y" />
  88. <el-option label="停用" value="N" />
  89. </el-select>
  90. </el-form-item>
  91. <el-form-item label=" ">
  92. <el-button type="primary" class="customer-bun-min" :disabled="!currentType.dictType" @click="loadDataList">查询</el-button>
  93. </el-form-item>
  94. </el-form>
  95. <el-table
  96. :data="dataList"
  97. :height="subTableHeight"
  98. border
  99. v-loading="dataLoading"
  100. style="width:100%">
  101. <el-table-column fixed="right" label="操作" header-align="center" align="center" width="160">
  102. <template slot-scope="scope">
  103. <el-link style="cursor:pointer" @click="openDataDialog(scope.row)">编辑 |</el-link>
  104. <el-link style="cursor:pointer" @click="handleDeleteData(scope.row)">删除 |</el-link>
  105. <el-link style="cursor:pointer" @click="handleDisableData(scope.row)">停用</el-link>
  106. </template>
  107. </el-table-column>
  108. <el-table-column prop="dictLabel" label="字典标签" min-width="120" header-align="center" align="left" show-overflow-tooltip />
  109. <el-table-column prop="dictValue" label="字典键值" min-width="120" header-align="center" align="left" show-overflow-tooltip />
  110. <el-table-column label="作用范围" width="80" header-align="center" align="center">
  111. <template slot-scope="scope">
  112. {{ formatDataScope(scope.row.site) }}
  113. </template>
  114. </el-table-column>
  115. <el-table-column prop="dictSort" label="排序" width="60" header-align="center" align="center" />
  116. <el-table-column prop="status" label="状态" width="80" header-align="center" align="center">
  117. <template slot-scope="scope">
  118. {{ scope.row.status === 'Y' ? '启用' : '停用' }}
  119. </template>
  120. </el-table-column>
  121. <el-table-column prop="isDefault" label="默认" width="70" header-align="center" align="center">
  122. <template slot-scope="scope">
  123. {{ scope.row.isDefault === 'Y' ? '是' : '否' }}
  124. </template>
  125. </el-table-column>
  126. <el-table-column prop="remark" label="备注" min-width="300" header-align="center" align="left" show-overflow-tooltip />
  127. <el-table-column prop="createBy" label="创建人" width="100" header-align="center" align="center" />
  128. <el-table-column prop="createDate" label="创建时间" min-width="140" header-align="center" align="center" />
  129. </el-table>
  130. <el-pagination
  131. style="margin-top:5px"
  132. @size-change="dataSizeChange"
  133. @current-change="dataPageChange"
  134. :current-page="dataPageIndex"
  135. :page-sizes="[20, 50, 100, 200]"
  136. :page-size="dataPageSize"
  137. :total="dataTotal"
  138. layout="total, sizes, prev, pager, next, jumper" />
  139. <div v-if="dataList.length === 0 && !dataLoading" style="margin-top:8px;color:#909399;font-size:12px;text-align:center">
  140. {{ currentType.dictType ? '暂无字典明细' : '请点击上方字典类型查看明细' }}
  141. </div>
  142. <!-- 字典明细弹窗 -->
  143. <el-dialog
  144. :title="dataDialogTitle"
  145. :visible.sync="dataDialogVisible"
  146. width="420px"
  147. :close-on-click-modal="false"
  148. append-to-body
  149. v-drag
  150. @close="resetDataForm">
  151. <el-form ref="dataFormRef" label-position="top" :model="dataForm" :rules="dataRules">
  152. <el-form-item label="字典类型">
  153. <el-input v-model="dataForm.dictType" disabled />
  154. </el-form-item>
  155. <el-form-item label="作用范围" prop="dataScope">
  156. <el-select v-model="dataForm.dataScope" :disabled="dataEditMode" placeholder="请选择" style="width:100%">
  157. <el-option label="所有工厂" value="all" />
  158. <el-option label="当前工厂" value="current" />
  159. </el-select>
  160. </el-form-item>
  161. <el-form-item label="字典标签" prop="dictLabel">
  162. <el-input v-model="dataForm.dictLabel" @blur="dictLabel()" maxlength="255" />
  163. </el-form-item>
  164. <el-form-item label="字典键值" prop="dictValue">
  165. <el-input v-model="dataForm.dictValue" :disabled="dataEditMode" maxlength="255" />
  166. </el-form-item>
  167. <el-form-item label="排序" prop="dictSort">
  168. <el-input-number v-model="dataForm.dictSort" :min="0" :controls="false" style="width:100%" />
  169. </el-form-item>
  170. <el-row :gutter="20">
  171. <el-col :span="12">
  172. <el-form-item label="状态" prop="status">
  173. <el-radio-group v-model="dataForm.status">
  174. <el-radio label="Y">启用</el-radio>
  175. <el-radio label="N">停用</el-radio>
  176. </el-radio-group>
  177. </el-form-item>
  178. </el-col>
  179. <el-col :span="12">
  180. <el-form-item label=" ">
  181. <el-checkbox v-model="dataForm.isDefault" true-label="Y" false-label="N">默认</el-checkbox>
  182. </el-form-item>
  183. </el-col>
  184. </el-row>
  185. <el-form-item label="备注">
  186. <el-input v-model="dataForm.remark" type="textarea" :rows="2" />
  187. </el-form-item>
  188. </el-form>
  189. <el-footer style="height:30px;margin-top: 30px;text-align:center">
  190. <el-button type="primary" :loading="dataSubmitLoading" @click="submitDataForm">保存</el-button>
  191. <el-button @click="dataDialogVisible = false">取消</el-button>
  192. </el-footer>
  193. </el-dialog>
  194. </div>
  195. </template>
  196. <script>
  197. import {
  198. listDictMaintenanceType,
  199. listDictMaintenanceData,
  200. saveDictMaintenanceData,
  201. updateDictMaintenanceData,
  202. deleteDictMaintenanceData,
  203. disableDictMaintenanceData,
  204. deleteDictMaintenanceType
  205. } from '@/api/base/dictMaintenance'
  206. const ALL_SITE = '*'
  207. export default {
  208. name: 'DictMaintenance',
  209. data () {
  210. const userSite = this.$store.state.user.site
  211. return {
  212. userSite,
  213. tableHeight: 200,
  214. subTableHeight: 200,
  215. typeLoading: false,
  216. dataLoading: false,
  217. dataSubmitLoading: false,
  218. typeList: [],
  219. dataList: [],
  220. dataListAll: [],
  221. typePageIndex: 1,
  222. typePageSize: 20,
  223. typeTotal: 0,
  224. dataPageIndex: 1,
  225. dataPageSize: 20,
  226. dataTotal: 0,
  227. typeSearch: {
  228. dictName: '',
  229. dictType: '',
  230. status: ''
  231. },
  232. dataSearch: {
  233. dictLabel: '',
  234. scope: '',
  235. status: ''
  236. },
  237. currentType: {},
  238. dataDialogVisible: false,
  239. dataDialogTitle: '新增字典明细',
  240. dataEditMode: false,
  241. dataForm: this.buildEmptyDataForm(),
  242. dataRules: {
  243. dataScope: [{ required: true, message: ' ', trigger: 'change' }],
  244. dictLabel: [{ required: true, message: ' ', trigger: 'blur' }],
  245. dictValue: [{ required: true, message: ' ', trigger: 'blur' }],
  246. dictSort: [{ required: true, type: 'number', message: ' ', trigger: 'blur' }],
  247. status: [{ required: true, message: ' ', trigger: 'change' }]
  248. }
  249. }
  250. },
  251. mounted () {
  252. this.calcHeight()
  253. window.addEventListener('resize', this.calcHeight)
  254. this.loadTypeList()
  255. },
  256. beforeDestroy () {
  257. window.removeEventListener('resize', this.calcHeight)
  258. },
  259. methods: {
  260. buildEmptyDataForm () {
  261. return {
  262. dictCode: undefined,
  263. dictLabel: '',
  264. dictValue: '',
  265. dictType: '',
  266. dictSort: 1,
  267. isDefault: 'N',
  268. status: 'Y',
  269. remark: '',
  270. site: ALL_SITE,
  271. dataScope: 'all',
  272. createBy: this.$store.state.user.name,
  273. updateBy: this.$store.state.user.name
  274. }
  275. },
  276. formatDataScope (site) {
  277. return site === ALL_SITE ? '所有工厂' : '当前工厂'
  278. },
  279. resolveDataSite (scope) {
  280. return scope === 'current' ? this.userSite : ALL_SITE
  281. },
  282. buildQueryParams (extra = {}) {
  283. return {
  284. site: this.userSite,
  285. ...extra
  286. }
  287. },
  288. applyDataFilterAndPage () {
  289. let rows = [...this.dataListAll]
  290. if (this.dataSearch.scope === 'all') {
  291. rows = rows.filter(r => r.site === ALL_SITE)
  292. } else if (this.dataSearch.scope === 'current') {
  293. rows = rows.filter(r => r.site === this.userSite)
  294. }
  295. rows.sort((a, b) => (a.dictSort || 0) - (b.dictSort || 0))
  296. this.dataTotal = rows.length
  297. const start = (this.dataPageIndex - 1) * this.dataPageSize
  298. this.dataList = rows.slice(start, start + this.dataPageSize)
  299. },
  300. calcHeight () {
  301. this.$nextTick(() => {
  302. const h = (window.innerHeight - 320) / 2
  303. this.tableHeight = h > 180 ? h : 180
  304. this.subTableHeight = h > 180 ? h : 180
  305. })
  306. },
  307. loadTypeList () {
  308. this.typeLoading = true
  309. listDictMaintenanceType(this.buildQueryParams({
  310. dictName: this.typeSearch.dictName,
  311. dictType: this.typeSearch.dictType,
  312. status: this.typeSearch.status
  313. })).then(({ data }) => {
  314. if (data && data.code === 0) {
  315. const rows = this.normalizeTypeRows(data.rows || [])
  316. this.applyTypeListResult(rows, rows.length)
  317. } else {
  318. this.$message.error((data && data.msg) || '查询字典类型失败')
  319. }
  320. }).catch(() => {
  321. this.$message.error('查询字典类型失败')
  322. }).finally(() => {
  323. this.typeLoading = false
  324. })
  325. },
  326. applyTypeListResult (rows, total) {
  327. const start = (this.typePageIndex - 1) * this.typePageSize
  328. this.typeList = rows.slice(start, start + this.typePageSize)
  329. this.typeTotal = total
  330. if (this.typeList.length > 0) {
  331. const currentId = this.currentType.dictId
  332. const matched = currentId ? this.typeList.find(r => r.dictId === currentId) : null
  333. const row = matched || this.typeList[0]
  334. this.$nextTick(() => {
  335. this.$refs.typeTable && this.$refs.typeTable.setCurrentRow(row)
  336. })
  337. this.selectDictType(row)
  338. } else {
  339. this.currentType = {}
  340. this.dataList = []
  341. this.dataTotal = 0
  342. }
  343. },
  344. dictLabel(){
  345. this.dataForm.dictValue = JSON.parse(JSON.stringify(this.dataForm.dictLabel));
  346. },
  347. normalizeTypeRows (rows) {
  348. if (!Array.isArray(rows)) {
  349. return []
  350. }
  351. return rows.map(row => this.normalizeTypeRow(row))
  352. },
  353. normalizeTypeRow (row) {
  354. const normalized = { ...(row || {}) }
  355. normalized.dictId = normalized.dictId != null ? normalized.dictId : normalized.dict_id
  356. normalized.dictType = this.firstText(
  357. normalized.dictType,
  358. normalized.dict_type
  359. )
  360. normalized.status = this.normalizeStatus(
  361. this.firstText(normalized.status, normalized.STATUS, normalized.active)
  362. )
  363. normalized.createBy = this.firstText(normalized.createBy, normalized.create_by)
  364. normalized.site = this.firstText(normalized.site, normalized.SITE)
  365. normalized.remark = this.firstText(normalized.remark)
  366. normalized.dictName = this.resolveTypeDisplayName(normalized)
  367. return normalized
  368. },
  369. resolveTypeDisplayName (row) {
  370. const direct = this.firstText(row && row.dictName, row && row.dict_name)
  371. if (direct && !this.isStatusText(direct)) {
  372. return direct
  373. }
  374. const remark = this.firstText(row && row.remark)
  375. if (remark) {
  376. const sep = remark.indexOf('-')
  377. if (sep > 0) {
  378. const prefix = remark.substring(0, sep).trim()
  379. if (prefix && !this.isStatusText(prefix)) {
  380. return prefix
  381. }
  382. }
  383. if (!this.isStatusText(remark)) {
  384. return remark
  385. }
  386. }
  387. return this.firstText(row && row.dictType, row && row.dict_type)
  388. },
  389. normalizeStatus (raw) {
  390. const text = this.firstText(raw)
  391. if (!text) {
  392. return ''
  393. }
  394. const upper = text.toUpperCase()
  395. if (upper === 'Y' || text === '启用') {
  396. return 'Y'
  397. }
  398. if (upper === 'N' || text === '停用') {
  399. return 'N'
  400. }
  401. return text
  402. },
  403. isStatusText (text) {
  404. const val = this.firstText(text)
  405. if (!val) {
  406. return false
  407. }
  408. const upper = val.toUpperCase()
  409. return upper === 'Y' || upper === 'N' || val === '启用' || val === '停用'
  410. },
  411. firstText (...vals) {
  412. for (const val of vals) {
  413. if (val === null || val === undefined) {
  414. continue
  415. }
  416. const text = String(val).trim()
  417. if (text) {
  418. return text
  419. }
  420. }
  421. return ''
  422. },
  423. selectDictType (row) {
  424. if (!row || !row.dictType) return
  425. this.currentType = { ...row }
  426. this.dataPageIndex = 1
  427. this.loadDataList()
  428. },
  429. loadDataList () {
  430. if (!this.currentType.dictType) {
  431. this.dataList = []
  432. this.dataListAll = []
  433. this.dataTotal = 0
  434. return
  435. }
  436. this.dataLoading = true
  437. listDictMaintenanceData(this.buildQueryParams({
  438. dictType: this.currentType.dictType,
  439. dictLabel: this.dataSearch.dictLabel,
  440. status: this.dataSearch.status
  441. })).then(({ data }) => {
  442. if (data && data.code === 0) {
  443. this.dataListAll = data.rows || []
  444. this.dataPageIndex = 1
  445. this.applyDataFilterAndPage()
  446. } else {
  447. this.$message.error((data && data.msg) || '查询字典明细失败')
  448. }
  449. }).catch(() => {
  450. this.$message.error('查询字典明细失败')
  451. }).finally(() => {
  452. this.dataLoading = false
  453. })
  454. },
  455. canDeleteType (row) {
  456. return !!(row && row.createBy === '订单扩展字段')
  457. },
  458. handleDeleteType (row) {
  459. if (!row || !row.dictType) {
  460. return
  461. }
  462. if (!this.canDeleteType(row)) {
  463. this.$message.warning('仅支持删除“订单扩展字段”自动维护的字典类型')
  464. return
  465. }
  466. const label = row.dictName || row.dictType
  467. this.$confirm('确定删除字典类型「' + label + '」及其明细数据吗?', '提示', { type: 'warning' }).then(() => {
  468. deleteDictMaintenanceType({
  469. dictId: row.dictId,
  470. dictType: row.dictType,
  471. site: row.site
  472. }).then(({ data }) => {
  473. if (data && data.code === 0) {
  474. this.$message.success(data.msg || '删除成功')
  475. this.currentType = {}
  476. this.dataList = []
  477. this.dataListAll = []
  478. this.dataTotal = 0
  479. this.loadTypeList()
  480. } else {
  481. this.$message.error((data && data.msg) || '删除失败')
  482. }
  483. }).catch(err => {
  484. this.$message.error((err && err.message) || '删除失败')
  485. })
  486. }).catch(() => {})
  487. },
  488. typeSizeChange (val) {
  489. this.typePageSize = val
  490. this.typePageIndex = 1
  491. this.loadTypeList()
  492. },
  493. typePageChange (val) {
  494. this.typePageIndex = val
  495. this.loadTypeList()
  496. },
  497. dataSizeChange (val) {
  498. this.dataPageSize = val
  499. this.dataPageIndex = 1
  500. this.applyDataFilterAndPage()
  501. },
  502. dataPageChange (val) {
  503. this.dataPageIndex = val
  504. this.applyDataFilterAndPage()
  505. },
  506. openDataDialog (row) {
  507. if (!this.currentType.dictType) {
  508. this.$message.warning('请先选择字典类型')
  509. return
  510. }
  511. if (row) {
  512. this.dataEditMode = true
  513. this.dataDialogTitle = '编辑字典明细'
  514. this.dataForm = {
  515. ...row,
  516. dataScope: row.site === ALL_SITE ? 'all' : 'current',
  517. updateBy: this.$store.state.user.name
  518. }
  519. } else {
  520. this.dataEditMode = false
  521. this.dataDialogTitle = '新增字典明细'
  522. this.dataForm = {
  523. ...this.buildEmptyDataForm(),
  524. dictType: this.currentType.dictType,
  525. dictSort: (this.dataListAll.length + 1) || 1
  526. }
  527. }
  528. this.dataDialogVisible = true
  529. },
  530. resetDataForm () {
  531. this.$refs.dataFormRef && this.$refs.dataFormRef.resetFields()
  532. this.dataForm = this.buildEmptyDataForm()
  533. },
  534. submitDataForm () {
  535. this.$refs.dataFormRef.validate(valid => {
  536. if (!valid) {
  537. this.$message.warning('请填写完整字典明细信息')
  538. return
  539. }
  540. this.dataForm.site = this.resolveDataSite(this.dataForm.dataScope)
  541. this.dataSubmitLoading = true
  542. const api = this.dataEditMode ? updateDictMaintenanceData : saveDictMaintenanceData
  543. api(this.dataForm).then(({ data }) => {
  544. if (data && data.code === 0) {
  545. this.$message.success(data.msg || '保存成功')
  546. this.dataDialogVisible = false
  547. this.loadDataList()
  548. } else {
  549. this.$message.error((data && data.msg) || '保存失败')
  550. }
  551. }).catch(err => {
  552. this.$message.error(err.message || '保存失败')
  553. }).finally(() => {
  554. this.dataSubmitLoading = false
  555. })
  556. })
  557. },
  558. handleDeleteData (row) {
  559. if (row.isSystem === 'Y') {
  560. this.$message.warning('系统字典不可删除')
  561. return
  562. }
  563. this.$confirm('确定删除字典明细「' + row.dictLabel + '」吗?', '提示', { type: 'warning' }).then(() => {
  564. deleteDictMaintenanceData(row).then(({ data }) => {
  565. if (data && data.code === 0) {
  566. this.$message.success(data.msg || '删除成功')
  567. this.loadDataList()
  568. } else {
  569. this.$message.error((data && data.msg) || '删除失败')
  570. }
  571. })
  572. }).catch(() => {})
  573. },
  574. handleDisableData (row) {
  575. if (row.isSystem === 'Y') {
  576. this.$message.warning('系统字典不可停用')
  577. return
  578. }
  579. this.$confirm('确定停用字典明细「' + row.dictLabel + '」吗?', '提示', { type: 'warning' }).then(() => {
  580. disableDictMaintenanceData(row).then(({ data }) => {
  581. if (data && data.code === 0) {
  582. this.$message.success(data.msg || '操作成功')
  583. this.loadDataList()
  584. } else {
  585. this.$message.error((data && data.msg) || '操作失败')
  586. }
  587. })
  588. }).catch(() => {})
  589. }
  590. }
  591. }
  592. </script>
  593. <style scoped>
  594. .pi-search-form { margin-top: 0; }
  595. .sub-title {
  596. font-weight: 600;
  597. margin: 16px 0 8px 0;
  598. padding-left: 4px;
  599. border-left: 4px solid #409eff;
  600. line-height: 1.2;
  601. color: #303133;
  602. }
  603. .customer-bun-min {
  604. margin-right: 8px;
  605. }
  606. .dialog-footer {
  607. text-align: center;
  608. }
  609. </style>