list.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. <template>
  2. <view>
  3. <view class="uni-header">
  4. <uni-stat-breadcrumb class="uni-stat-breadcrumb-on-phone"/>
  5. </view>
  6. <view class="uni-tabs__header">
  7. <view class="uni-tabs__nav-wrap">
  8. <view class="uni-tabs__nav-scroll">
  9. <view class="uni-tabs__nav">
  10. <view @click="switchTab('menus')" :class="{'is-active':currentTab==='menus'}"
  11. class="uni-tabs__item">
  12. {{ $t('menu.text.menuManager') }}
  13. </view>
  14. <view @click="switchTab('pluginMenus')" v-if="pluginMenus.length"
  15. :class="{'is-active':currentTab==='pluginMenus'}" class="uni-tabs__item">
  16. {{ $t('menu.text.additiveMenu') }}
  17. <uni-badge class="menu-badge" :text="pluginMenus.length" type="error"></uni-badge>
  18. </view>
  19. </view>
  20. </view>
  21. </view>
  22. </view>
  23. <view v-show="currentTab==='menus'">
  24. <view class="uni-header" style="border-bottom: 0;margin-bottom: -15px;">
  25. <view class="uni-group">
  26. <button @click="navigateTo('./add')" size="mini" plain="true"
  27. type="primary">{{ $t('menu.button.addFirstLevelMenu') }}
  28. </button>
  29. <button @click="updateBuiltInMenu" size="mini" plain="true" style="margin-left: 10px;"
  30. type="warn">{{ $t('menu.button.updateBuiltInMenu') }}
  31. </button>
  32. </view>
  33. <view class="uni-group">
  34. </view>
  35. </view>
  36. <view class="uni-container">
  37. <unicloud-db ref="udb" @load="onqueryload" collection="opendb-admin-menus" :options="options"
  38. :where="where" page-data="replace" :orderby="orderby" :getcount="true"
  39. :page-size="options.pageSize"
  40. :page-current="options.pageCurrent" v-slot:default="{data,pagination,loading,error}">
  41. <uni-table :loading="loading" class="table-pc" :emptyText="errMsg || $t('common.empty')" border
  42. stripe>
  43. <uni-tr>
  44. <uni-th align="center">排序</uni-th>
  45. <uni-th width="200" align="center">名称</uni-th>
  46. <uni-th align="center">标识</uni-th>
  47. <uni-th align="center">URL</uni-th>
  48. <uni-th width="100" align="center">是否启用</uni-th>
  49. <uni-th align="center">操作</uni-th>
  50. </uni-tr>
  51. <uni-tr v-for="(item,index) in data" :key="index">
  52. <uni-td align="center">{{ item.sort }}</uni-td>
  53. <uni-td>{{ item.name }}</uni-td>
  54. <uni-td>{{ item.menu_id }}</uni-td>
  55. <uni-td>{{ item.url }}</uni-td>
  56. <uni-td align="center" :class="{'menu-disable':!item.enable}">
  57. <switch :checked="item.enable" @change="enableChange(item)" />
  58. <!-- {{ item.enable ? '已启用' : '未启用' }} -->
  59. </uni-td>
  60. <uni-td align="center">
  61. <view class="uni-group" style="justify-content: left;">
  62. <button @click="navigateTo('./edit?id='+item._id, false)" class="uni-button"
  63. size="mini" type="primary">{{ $t('common.button.edit') }}
  64. </button>
  65. <button
  66. v-if="item.menu_id !== 'system_menu' && item.menu_id !== 'system_management'"
  67. @click="confirmDelete(item)" class="uni-button" size="mini"
  68. type="warn">{{ $t('common.button.delete') }}
  69. </button>
  70. <button v-if="!item.url" @click="navigateTo('./add?parent_id='+item.menu_id, false)"
  71. class="uni-button" size="mini"
  72. type="primary">{{ $t('menu.button.addChildMenu') }}
  73. </button>
  74. </view>
  75. </uni-td>
  76. </uni-tr>
  77. </uni-table>
  78. </unicloud-db>
  79. </view>
  80. </view>
  81. <view v-show="currentTab==='pluginMenus'">
  82. <view class="uni-header" style="border-bottom: 0;margin-bottom: -15px;">
  83. <view class="uni-group">
  84. <button style="width: 130px;" @click="addPluginMenus" size="mini" type="primary">添加选中的菜单
  85. </button>
  86. </view>
  87. <view class="uni-group"></view>
  88. </view>
  89. <view class="uni-container">
  90. <uni-table ref="pluginMenusTable" type="selection" border stripe
  91. @selection-change="pluginMenuSelectChange">
  92. <uni-tr>
  93. <uni-th align="center">名称(标识)</uni-th>
  94. <uni-th align="center">URL</uni-th>
  95. <uni-th align="center">插件菜单 json 文件</uni-th>
  96. </uni-tr>
  97. <uni-tr v-for="(item,index) in pluginMenus" :key="index">
  98. <uni-td>{{ item.name }}({{ item.menu_id }})</uni-td>
  99. <uni-td>{{ item.url }}</uni-td>
  100. <uni-td>{{ item.json }}</uni-td>
  101. </uni-tr>
  102. </uni-table>
  103. <view class="uni-sub-title" style="margin-top: 15px;">
  104. 以上待添加菜单来自于三方插件,添加后,将显示在菜单管理中,若不希望显示在上述表格中时,可手动删除项目中对应的`插件id-menu.json`文件。
  105. </view>
  106. </view>
  107. </view>
  108. <!-- #ifndef H5 -->
  109. <fix-window/>
  110. <!-- #endif -->
  111. </view>
  112. </template>
  113. <script>
  114. import {
  115. buildMenus
  116. } from '../../../components/uni-data-menu/util.js'
  117. import originalMenuList from './originalMenuList.json'
  118. const db = uniCloud.database()
  119. // 表查询配置
  120. const dbOrderBy = 'create_date asc'
  121. // 分页配置
  122. const pageSize = 20000
  123. const pageCurrent = 1
  124. // 查找插件注册的菜单列表(目前仅在开发模式启用,仅限 admin 角色)
  125. const pluginMenuJsons = []
  126. if (process.env.NODE_ENV === 'development') {
  127. // #ifdef VUE2
  128. const rootModules = require.context(
  129. '../../../',
  130. false,
  131. /-menu.json$/
  132. )
  133. rootModules.keys().forEach(function (key) {
  134. const json = key.substr(2)
  135. rootModules(key).forEach(item => {
  136. item.json = json
  137. pluginMenuJsons.push(item)
  138. })
  139. })
  140. const pluginModules = require.context(
  141. '../../../uni_modules/',
  142. true,
  143. /menu.json$/
  144. )
  145. pluginModules.keys().forEach(function (key) {
  146. const json = 'uni_modules' + key.substr(1)
  147. pluginModules(key).forEach(item => {
  148. item.json = json
  149. pluginMenuJsons.push(item)
  150. })
  151. })
  152. // #endif
  153. // #ifdef VUE3
  154. const rootModules = import.meta.glob('../../../uni_modules/*/*-menu.json', {eager: true});
  155. for (const modulePath in rootModules) {
  156. const json = modulePath.replace(/^..\/..\/..\//, '');
  157. let moduleItem = rootModules[modulePath];
  158. if (typeof moduleItem === "function") {
  159. // 兼容 HBX3.6.5或以下版本
  160. moduleItem().then(module => {
  161. module = module.default ? module.default : module
  162. module.forEach(item => {
  163. item.json = json
  164. pluginMenuJsons.push(item)
  165. });
  166. })
  167. } else {
  168. // 兼容 HBX3.6.13或以上版本
  169. let module = moduleItem.default ? moduleItem.default : moduleItem;
  170. module.forEach(item => {
  171. item.json = json
  172. pluginMenuJsons.push(item)
  173. });
  174. }
  175. }
  176. const pluginModules = import.meta.glob('../../../uni_modules/**/menu.json', {eager: true});
  177. for (const modulePath in pluginModules) {
  178. const json = modulePath.replace(/^..\/..\/..\//, '');
  179. let moduleItem = pluginModules[modulePath];
  180. if (typeof moduleItem === "function") {
  181. // 兼容 HBX3.6.5或以下版本
  182. moduleItem().then(module => {
  183. module = module.default ? module.default : module
  184. module.forEach(item => {
  185. item.json = json
  186. pluginMenuJsons.push(item)
  187. })
  188. })
  189. } else {
  190. // 兼容 HBX3.6.13或以上版本
  191. let module = moduleItem.default ? moduleItem.default : moduleItem;
  192. module.forEach(item => {
  193. item.json = json
  194. pluginMenuJsons.push(item)
  195. });
  196. }
  197. }
  198. // #endif
  199. }
  200. // 获取父的个数
  201. function getParents(menus, id, depth = 0) {
  202. menus.forEach(menu => {
  203. if (menu.menu_id === id && menu.parent_id) {
  204. depth = depth + 1 + getParents(menus, menu.parent_id, depth)
  205. }
  206. })
  207. return depth
  208. }
  209. // 获取子的 _id
  210. function getChildren(menus, id, childrenIds = []) {
  211. if (menus.find(menu => menu.parent_id === id)) {
  212. menus.forEach(item => {
  213. if (item.parent_id === id) {
  214. childrenIds.push(item._id)
  215. getChildren(menus, item.menu_id, childrenIds)
  216. }
  217. })
  218. }
  219. return childrenIds
  220. }
  221. export default {
  222. data() {
  223. return {
  224. query: '',
  225. where: '',
  226. orderby: dbOrderBy,
  227. options: {
  228. pageSize,
  229. pageCurrent
  230. },
  231. selectedIndexs: [], //批量选中的项
  232. loading: true,
  233. menus: [],
  234. errMsg: '',
  235. currentTab: 'menus',
  236. selectedPluginMenuIndexs: []
  237. }
  238. },
  239. computed: {
  240. pluginMenus() {
  241. const menus = []
  242. if (!this.$hasRole('admin')) {
  243. return menus
  244. }
  245. const dbMenus = this.menus
  246. if (!dbMenus.length) {
  247. return menus
  248. }
  249. pluginMenuJsons.forEach(menu => {
  250. // 查找尚未被注册到数据库中的菜单
  251. if (!dbMenus.find(item => item.menu_id === menu.menu_id)) {
  252. menus.push(menu)
  253. }
  254. })
  255. return menus
  256. },
  257. },
  258. watch: {
  259. pluginMenus(val) {
  260. if (!val.length) {
  261. this.currentTab = 'menus'
  262. }
  263. }
  264. },
  265. methods: {
  266. enableChange(item){
  267. item.enable = item.enable ? false : true;
  268. db.collection("opendb-admin-menus").doc(item._id).update({
  269. enable: item.enable
  270. });
  271. },
  272. getSortMenu(menuList) {
  273. // 标记叶子节点
  274. menuList.map(item => {
  275. if (!menuList.some(subMenuItem => subMenuItem.parent_id === item.menu_id)) {
  276. item.isLeafNode = true
  277. }
  278. })
  279. return buildMenus(menuList)
  280. },
  281. onqueryload(data) {
  282. for (let i = 0; i < data.length; i++) {
  283. let item = data[i]
  284. const depth = getParents(data, item.menu_id)
  285. item.name = (depth ? ' '.repeat(depth) + '|-' : '') + item.name
  286. }
  287. const menuTree = this.getSortMenu(data)
  288. const sortMenus = []
  289. this.patTree(menuTree, sortMenus)
  290. data.length = 0;
  291. data.push(...sortMenus)
  292. this.menus = data //仅导出当前页
  293. },
  294. patTree(tree, sortMenus) {
  295. tree.forEach(item => {
  296. sortMenus.push(item)
  297. if (item.children.length) {
  298. this.patTree(item.children, sortMenus)
  299. }
  300. })
  301. return sortMenus
  302. },
  303. switchTab(tab) {
  304. this.currentTab = tab
  305. },
  306. loadData(clear = true) {
  307. this.$refs.udb.loadData({
  308. clear
  309. })
  310. },
  311. navigateTo(url, clear) { // clear 表示刷新列表时是否清除当前页码,true 表示刷新并回到列表第 1 页,默认为 true
  312. uni.navigateTo({
  313. url,
  314. events: {
  315. refreshData: () => {
  316. this.loadData(clear)
  317. }
  318. }
  319. })
  320. },
  321. confirmDelete(menu) {
  322. let ids = menu._id
  323. let content = '是否删除该菜单?'
  324. // 如有子菜单
  325. const children = getChildren(this.menus, menu.menu_id)
  326. if (children.length) content = '是否删除该菜单及其子菜单?'
  327. ids = [ids, ...children]
  328. uni.showModal({
  329. title: '提示',
  330. content,
  331. success: (res) => {
  332. if (!res.confirm) {
  333. return
  334. }
  335. this.$refs.udb.remove(ids, {
  336. needConfirm: false
  337. })
  338. }
  339. })
  340. },
  341. pluginMenuSelectChange(e) {
  342. this.selectedPluginMenuIndexs = e.detail.index
  343. },
  344. addPluginMenus(confirmContent) {
  345. if (!this.selectedPluginMenuIndexs.length) {
  346. return uni.showModal({
  347. title: '提示',
  348. content: '请选择要添加的菜单!',
  349. showCancel: false
  350. })
  351. }
  352. const pluginMenus = this.pluginMenus
  353. const menus = []
  354. this.selectedPluginMenuIndexs.forEach(i => {
  355. const menu = pluginMenus[i]
  356. if (menu) {
  357. // 拷贝一份,移除 json 字段
  358. const dbMenu = JSON.parse(JSON.stringify(menu))
  359. dbMenu.enable = true;
  360. delete dbMenu.json
  361. menus.push(dbMenu)
  362. }
  363. })
  364. uni.showModal({
  365. title: '提示',
  366. content: '您确认要添加已选中的菜单吗?',
  367. success: (res) => {
  368. if (!res.confirm) {
  369. return
  370. }
  371. uni.showLoading({
  372. mask: true
  373. })
  374. const checkAll = menus.length === pluginMenus.length
  375. uniCloud.database().collection('opendb-admin-menus').add(menus).then(res => {
  376. // this.init()
  377. uni.showModal({
  378. title: '提示',
  379. content: '添加菜单成功!',
  380. showCancel: false,
  381. success: () => {
  382. this.$refs.pluginMenusTable.clearSelection()
  383. if (checkAll) {
  384. this.currentTab = 'menus'
  385. }
  386. this.loadData()
  387. }
  388. })
  389. }).catch(err => {
  390. uni.showModal({
  391. title: '提示',
  392. content: err.message,
  393. showCancel: false
  394. })
  395. }).finally(() => {
  396. uni.hideLoading()
  397. })
  398. }
  399. })
  400. },
  401. // 更新内置菜单
  402. async updateBuiltInMenu(){
  403. uni.showModal({
  404. title: '提示',
  405. content: '确定更新内置菜单吗?\n(该操作不会影响现有的菜单)',
  406. success: async (res) => {
  407. if (res.confirm) {
  408. const db = uniCloud.database();
  409. const _ = db.command;
  410. let menu_ids = originalMenuList.map((item, index) => {
  411. return item.menu_id;
  412. });
  413. uni.showLoading({
  414. title:"更新中...",
  415. mask:true
  416. });
  417. try {
  418. let addMenuList = [];
  419. // 读取菜单
  420. let oldMenuListRes = await db.collection("opendb-admin-menus").where({
  421. menu_id: _.in[menu_ids]
  422. }).limit(500).get();
  423. let oldMenuList = oldMenuListRes.result.data;
  424. originalMenuList.map((item, index) => {
  425. let oldMenuItem = oldMenuList.find((item2, index2, arr2) => {
  426. return item2.menu_id === item.menu_id;
  427. });
  428. if (!oldMenuItem) {
  429. addMenuList.push({
  430. ...item,
  431. create_date: undefined
  432. });
  433. }
  434. });
  435. if (addMenuList && addMenuList.length > 0) {
  436. // 添加没有的菜单
  437. let addRes = await db.collection("opendb-admin-menus").add(addMenuList);
  438. uni.showToast({
  439. title:`新增了${addRes.result.inserted}个菜单,即将刷新`,
  440. icon:"none"
  441. })
  442. setTimeout(() => {
  443. // #ifdef H5
  444. window.location.reload();
  445. // #endif
  446. // #ifndef H5
  447. this.loadData(true);
  448. // #endif
  449. }, 300);
  450. } else {
  451. uni.showToast({
  452. title:"菜单无变动",
  453. icon:"none"
  454. })
  455. }
  456. } catch(err) {
  457. console.error(err)
  458. } finally {
  459. uni.hideLoading();
  460. }
  461. }
  462. }
  463. });
  464. }
  465. }
  466. }
  467. </script>
  468. <style>
  469. /* #ifndef H5 */
  470. page {
  471. padding-top: 85px;
  472. }
  473. /* #endif */
  474. .menu-disable {
  475. color: red;
  476. }
  477. .menu-badge {
  478. position: absolute;
  479. top: 0;
  480. right: 5px;
  481. }
  482. </style>