adminMenus.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. <template>
  2. <a-card :bordered="false" :bodyStyle="{overflowY: 'auto'}">
  3. <a-row :gutter="24">
  4. <a-button style="margin-right: 10px;" type="primary" icon="plus" @click="addChild">添加子菜单</a-button>
  5. <a-button style="margin-right: 10px;background-color: #19be6b;color: #fff;" type="" icon="plus" @click="addTitle">添加顶部菜单</a-button>
  6. <a-button style="margin-right: 10px;background-color: red;color: #fff;" type="" icon="delete" @click="delect()">删除</a-button>
  7. </a-row>
  8. <a-row :gutter="24">
  9. <!-- 左侧菜单树列表 -->
  10. <a-col :span="8">
  11. <a-row :gutter="24" class="textCount">
  12. <a-icon type="info-circle" style="color: #1890FF;margin-right: 10px;" />
  13. <span>当前选择:<span style="color: #40a9ff;font-weight: 600;">{{ selectedMenu }}</span><span v-if="selectedKeys.length" class="imgSize" @click="cancelSel">取消选择</span></span>
  14. </a-row>
  15. <a-row :gutter="24" >
  16. <a-input-search placeholder="输入搜索菜单名搜索" style="width: 100%" @change="onChange" />
  17. <div style="max-height: 600px;overflow: auto;">
  18. <a-tree
  19. @expand="onExpand"
  20. :treeData="getMenus"
  21. :expandedKeys="expandedKeys"
  22. :autoExpandParent="autoExpandParent"
  23. @select="onSelect"
  24. :selectedKeys="selectedKeys"
  25. showIcon
  26. >
  27. <template slot="title" slot-scope="{title}">
  28. <span v-if="title.indexOf(searchValue) > -1">
  29. {{ title.substr(0, title.indexOf(searchValue)) }}
  30. <span style="color: #f50">{{ searchValue }}</span>
  31. {{ title.substr(title.indexOf(searchValue) + searchValue.length) }}
  32. </span>
  33. <span v-else>{{ title }}</span>
  34. </template>
  35. <a-icon slot="file" type="file" />
  36. <a-icon slot="pushpin" type="pushpin" />
  37. </a-tree>
  38. </div>
  39. </a-row>
  40. </a-col>
  41. <!-- 右侧 -->
  42. <a-col :span="16" >
  43. <a-form :form="form" @submit="handleSubmit">
  44. <!-- 类型 -->
  45. <a-form-item label="类型" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  46. <div v-if="!formData.type||formData.type=='0'">
  47. <a-icon type="file" style="margin-right: 10px;" /><span >页面菜单</span>
  48. </div>
  49. <div v-if="formData.type=='1'">
  50. <a-icon type="pushpin" style="margin-right: 10px;" /><span >操作按钮</span>
  51. </div>
  52. </a-form-item>
  53. <a-form-item label="ID" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  54. {{ formData.id }}
  55. </a-form-item>
  56. <a-form-item label="父节点ID" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  57. <a-input
  58. placeholder="请输入父节点ID(最多50个字符)"
  59. :maxLength="50"
  60. v-decorator="['formData.parentId', {
  61. initialValue: formData.parentId,
  62. getValueFromEvent: $filterEmpty,
  63. rules: [{ required: true, message: '请输入父节点ID!' }] }]"
  64. />
  65. </a-form-item>
  66. <!-- 名称 -->
  67. <a-form-item label="名称" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  68. <a-input
  69. placeholder="请输入名称(最多30个字符)"
  70. v-decorator="['formData.name', {
  71. initialValue: formData.name,
  72. getValueFromEvent: $filterEmpty,
  73. rules: [{ required: true, message: '请输入名称!' }] }]"
  74. />
  75. </a-form-item>
  76. <!-- 前端权限编码 -->
  77. <a-form-item label="前端权限编码" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  78. <a-input
  79. placeholder="请输入前端权限编码"
  80. v-decorator="[
  81. 'formData.code',
  82. {
  83. initialValue: formData.code,
  84. rules: [{ required: true, message: '请输入前端权限编码!' }] },
  85. ]"
  86. />
  87. </a-form-item>
  88. <!-- 后台权限编码 -->
  89. <a-form-item label="后台权限编码" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  90. <a-textarea
  91. placeholder="请输入后台权限编码"
  92. :auto-size="{ minRows: 2, maxRows: 6 }"
  93. v-decorator="[
  94. 'formData.permission',
  95. {
  96. initialValue: formData.permission,
  97. rules: [{ required: formData.isLeaf, message: '请输入后台权限编码!' }] },
  98. ]"
  99. />
  100. </a-form-item>
  101. <a-form-item label="价格权限" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  102. <a-checkbox-group v-model="authPrice" :options="priceOptions" @change="onPriceChange" />
  103. </a-form-item>
  104. <!-- 排序值 -->
  105. <a-form-item label="排序值" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  106. <a-input-number
  107. style="width: 100%;"
  108. type="number"
  109. placeholder="请输入排序值(0~999999)"
  110. v-decorator="['formData.sort', {
  111. initialValue: formData.sort,
  112. rules: [] }]"
  113. :max="999999"
  114. :min="0"
  115. />
  116. </a-form-item>
  117. <!-- 状态 -->
  118. <a-form-item label="启用" :label-col="{ span: 5 }" :wrapper-col="{ span: 14 }">
  119. <a-radio-group
  120. name="radioGroup"
  121. v-decorator="[
  122. 'formData.status',
  123. {
  124. initialValue: formData.status,
  125. rules: [{ required: true, message: '请选择状态!' }] },
  126. ]"
  127. >
  128. <a-radio :value="1">是</a-radio>
  129. <a-radio :value="0">否</a-radio>
  130. </a-radio-group>
  131. </a-form-item>
  132. <a-form-item :wrapper-col="{ span: 12, offset: 5 }">
  133. <a-button size="large" type="primary" @click="handleSubmit()">
  134. 保存
  135. </a-button>
  136. </a-form-item>
  137. </a-form>
  138. </a-col>
  139. </a-row>
  140. <childModal parentType="admin" :visible="showChildModal" @refresh="getYyMenuList" :data="parentData" @close="showChildModal = false"></childModal>
  141. </a-card>
  142. </template>
  143. <script>
  144. import { commonMixin } from '@/utils/mixin'
  145. import { STable, VSelect } from '@/components'
  146. import childModal from './childModal.vue'
  147. import { getYyMenuList, saveYyMenu, deleteYyMenu } from '@/api/menu.js'
  148. export default {
  149. name: 'AdminMenus',
  150. mixins: [commonMixin],
  151. components: {
  152. STable, VSelect, childModal
  153. },
  154. props: {},
  155. data () {
  156. return {
  157. priceOptions:[],
  158. authPrice: [],
  159. treeData: [],
  160. dataList: [],
  161. searchValue: '',
  162. expandedKeys: [],
  163. autoExpandParent: true,
  164. selectedKeys: [],
  165. selectedMenu: '', // 选择的树节点名
  166. formLayout: 'horizontal',
  167. form: this.$form.createForm(this, { name: 'menu' }),
  168. showChildModal: false,
  169. parentData: {}, // 父级菜单
  170. currMenusData:null,
  171. formData: {
  172. id: '',
  173. name: '',
  174. routePath: '',
  175. icon: '',
  176. routeName: '',
  177. componentPath: '',
  178. urlPath: '',
  179. code: '',
  180. permission: '',
  181. sort: '',
  182. type: '0',
  183. isLeaf: true,
  184. status: '' // 活动状态
  185. }
  186. }
  187. },
  188. computed: {
  189. getMenus () {
  190. this.generateList(this.treeData)
  191. return this.treeData
  192. }
  193. },
  194. methods: {
  195. onPriceChange(val){
  196. console.log(val,this.authPrice,this.currMenusData)
  197. const data = this.currMenusData
  198. if(!data){
  199. this.$message.warning('请选择一个父级菜单')
  200. this.authPrice = []
  201. return false
  202. }
  203. // 当前选中节点的价格code
  204. const child = data&&data.dataRef&&data.dataRef.children ? data.dataRef.children : []
  205. data.dataRef.children = child
  206. const curAuth = []
  207. child.map(item=>{
  208. const a = item.code.split('_')
  209. const b = a[a.length-1]
  210. const c = this.priceOptions.find(item => item.value == b)
  211. if(c){
  212. curAuth.push(a[a.length-1])
  213. }
  214. })
  215. // 添加
  216. if(val.length > curAuth.length){
  217. const diff = val.filter(item => !new Set(curAuth).has(item))
  218. console.log(diff)
  219. const n = this.priceOptions.find(item=>item.value == diff[0])
  220. this.addChildPrice({
  221. parentId: data.id,
  222. name: n.label,
  223. code: data.code +'_'+diff[0],
  224. status: 1,
  225. sort: curAuth.length
  226. },function(ret){
  227. ret.title = ret.name
  228. child.push(ret)
  229. })
  230. }else{
  231. // 删除
  232. const diff = curAuth.filter(item => !new Set(val).has(item))
  233. const idx = child.findIndex(item => {
  234. const a = item.code.split('_')
  235. return a[a.length-1] == diff[0]
  236. })
  237. console.log(child[idx])
  238. this.delChildPrice(child[idx].id,function(){
  239. child.splice(idx,1)
  240. })
  241. }
  242. },
  243. onExpand (expandedKeys) {
  244. // console.log('onExpand', expandedKeys)
  245. this.expandedKeys = expandedKeys
  246. this.autoExpandParent = false
  247. },
  248. onSelect (selectedKeys, info) {
  249. this.selectedKeys = selectedKeys
  250. if (info.selected) {
  251. this.clear()
  252. const data = info.selectedNodes[0].data.props
  253. const isLeaf = data.dataRef.children || []
  254. console.log('onSelect', isLeaf)
  255. this.currMenusData = data // 当前节点的子级
  256. // 价格权限回显
  257. isLeaf.map(item => {
  258. const a = item.code.split('_')
  259. this.authPrice.push(a[a.length-1])
  260. })
  261. this.formData = {
  262. id: data.id,
  263. parentId: data.parentId,
  264. name: data.name,
  265. routePath: data.routePath,
  266. icon: data.icon,
  267. routeName: data.routeName,
  268. componentPath: data.componentPath,
  269. urlPath: data.urlPath,
  270. code: data.code,
  271. permission: data.permission,
  272. sort: data.sort,
  273. type: data.type,
  274. status: data.status,
  275. isLeaf: !(isLeaf && isLeaf.length)
  276. }
  277. this.selectedMenu = this.formData.name
  278. this.formData.status = Number(this.formData.status)
  279. } else {
  280. this.selectedMenu = ''
  281. this.clear()
  282. }
  283. },
  284. generateList (data) {
  285. for (let i = 0; i < data.length; i++) {
  286. const node = data[i]
  287. const key = node.title
  288. this.dataList.push({ key, title: key })
  289. node.scopedSlots = { title: 'title' }
  290. node.slots = { icon: node.type == '0' ? 'file' : 'pushpin' }
  291. if (node.children) {
  292. this.generateList(node.children)
  293. }
  294. }
  295. },
  296. getParentKey (key, tree) {
  297. let parentKey
  298. for (let i = 0; i < tree.length; i++) {
  299. const node = tree[i]
  300. if (node.children) {
  301. if (node.children.some(item => item.title == key)) {
  302. parentKey = node.key
  303. } else if (this.getParentKey(key, node.children)) {
  304. parentKey = this.getParentKey(key, node.children)
  305. }
  306. }
  307. }
  308. return parentKey
  309. },
  310. onChange (e) {
  311. const value = e.target.value
  312. const expandedKeys = this.dataList
  313. .map(item => {
  314. if (item.title.indexOf(value) > -1) {
  315. return this.getParentKey(item.title, this.treeData)
  316. }
  317. return null
  318. })
  319. .filter((item, i, self) => item && self.indexOf(item) === i)
  320. Object.assign(this, {
  321. expandedKeys,
  322. searchValue: value,
  323. autoExpandParent: true
  324. })
  325. },
  326. // 获取菜单树列表
  327. getYyMenuList () {
  328. getYyMenuList().then(res => {
  329. if (res.status == 200) {
  330. this.treeData = res.data
  331. this.$store.state.app.authMenusList = res.data
  332. }
  333. })
  334. },
  335. // 添加子节点
  336. addChild () {
  337. if (!this.selectedMenu) {
  338. return this.$message.warning('请先选择一个父级菜单')
  339. } else if (this.formData.type == '1') {
  340. return this.$message.warning('请选择一个页面级菜单')
  341. }
  342. this.showChildModal = true
  343. this.parentData = this.formData
  344. },
  345. // 添加顶部菜单
  346. addTitle () {
  347. this.showChildModal = true
  348. this.parentData = {}
  349. },
  350. // 取消选择
  351. cancelSel () {
  352. this.selectedKeys = []
  353. this.selectedMenu = ''
  354. this.clear()
  355. },
  356. // 清空列表
  357. clear () {
  358. this.form.resetFields()
  359. this.formData.id = ''
  360. this.formData.parentId = ''
  361. this.formData.name = ''
  362. this.formData.routePath = ''
  363. this.formData.icon = ''
  364. this.formData.routeName = ''
  365. this.formData.componentPath = ''
  366. this.formData.urlPath = ''
  367. this.formData.sort = ''
  368. this.formData.type = ''
  369. this.formData.status = ''
  370. this.formData.code = ''
  371. this.formData.permission = ''
  372. this.formData.isLeaf = true
  373. this.currMenusData = null
  374. this.authPrice = []
  375. },
  376. // 删除数据
  377. delect () {
  378. if (!this.selectedKeys.length) {
  379. return this.$message.warning('您还未选择要删除的数据')
  380. }
  381. const _this = this
  382. console.log(_this.formData.id, '_this.formData.id')
  383. this.$confirm({
  384. title: '警告',
  385. content: `确定要删除您选择的 ${_this.formData.name} 的数据吗?`,
  386. okText: '确定',
  387. cancelText: '取消',
  388. onOk () {
  389. _this.delChildPrice(_this.formData.id)
  390. }
  391. })
  392. },
  393. // 删除子级
  394. delChildPrice(id,callback){
  395. const _this = this
  396. deleteYyMenu({
  397. id: id
  398. }).then(res => {
  399. if (res.status == 200) {
  400. _this.$message.success('删除成功')
  401. _this.getYyMenuList()
  402. callback&&callback()
  403. }
  404. }).catch(() => {
  405. _this.$message.success('删除失败, 请稍后重试')
  406. })
  407. },
  408. // 保存提交
  409. handleSubmit () {
  410. const _this = this
  411. this.form.validateFields((err, values) => {
  412. if (!err) {
  413. if (values.formData.permission) {
  414. values.formData.permission = values.formData.permission.replace(/\//g, '_')
  415. }
  416. this.addChildPrice(Object.assign({ id: this.formData.id }, values.formData))
  417. }
  418. })
  419. },
  420. // 新增子级
  421. addChildPrice(data,callback){
  422. saveYyMenu(data).then(res => {
  423. if (res.status + '' === '200') {
  424. this.$message.success(res.message ? res.message : '保存成功')
  425. this.getYyMenuList()
  426. callback&&callback(res.data)
  427. }
  428. })
  429. },
  430. handleCancel () {
  431. this.clear()
  432. }
  433. },
  434. mounted () {
  435. this.priceOptions = this.$store.state.app.priceAuthOptions
  436. },
  437. beforeCreate () {
  438. this.form = this.$form.createForm(this, { name: 'menu' })
  439. },
  440. beforeRouteEnter (to, from, next) {
  441. next(vm => {
  442. vm.getYyMenuList()
  443. })
  444. },
  445. watch: {
  446. }
  447. }
  448. </script>
  449. <style>
  450. .menu-box::-webkit-scrollbar{
  451. width: 6px;
  452. height: 6px ;
  453. }
  454. .menu-box::-webkit-scrollbar-thumb{
  455. width: 6px;
  456. height: 6px ;
  457. border-radius: 4px;
  458. -webkit-box-shadow: inset 0 0 2px #d1d1d1;
  459. background: #e4e4e4;
  460. }
  461. .textCount{
  462. border: 1px solid #91d5ff;
  463. background-color: #e6f7ff;
  464. border-radius: 5px;
  465. padding: 5px 10px 5px 20px;
  466. margin: 10px 0;
  467. color: rgba(0, 0, 0);
  468. }
  469. .imgSize{
  470. color: #2d8cf0;
  471. margin-left: 10px;
  472. font-size: 14px;
  473. }
  474. </style>