Some checks failed
CI / init (pull_request) Has been cancelled
CI / Frontend node 18.16.0 (pull_request) Has been cancelled
CI / Backend go (1.22) (pull_request) Has been cancelled
CI / release-pr (pull_request) Has been cancelled
CI / devops-test (1.22, 18.16.0) (pull_request) Has been cancelled
CI / release-please (pull_request) Has been cancelled
CI / devops-prod (1.22, 18.x) (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
242 lines
7.2 KiB
JavaScript
242 lines
7.2 KiB
JavaScript
/**
|
||
* 书籍后台管理菜单创建脚本
|
||
*
|
||
* 使用方式:
|
||
* 1. 登录后台管理前端。
|
||
* 2. 打开浏览器控制台。
|
||
* 3. 如前端接口代理不是 /api,先执行:
|
||
* window.__XUANZHI_BASE_API__ = 'http://127.0.0.1:8888'
|
||
* 4. 粘贴本文件全部内容并执行。
|
||
*
|
||
* 说明:
|
||
* - 脚本会先读取现有菜单树,已存在的菜单不会重复创建。
|
||
* - /menu/addBaseMenu 不返回新菜单 ID,所以每次创建后会重新读取菜单树定位 ID。
|
||
*/
|
||
(async () => {
|
||
const BASE_API = window.__XUANZHI_BASE_API__ || '/api'
|
||
const TOKEN = window.__XUANZHI_TOKEN__ || localStorage.getItem('token') || getCookie('x-token')
|
||
let userId = window.__XUANZHI_USER_ID__ || localStorage.getItem('userId') || ''
|
||
|
||
const menus = [
|
||
{
|
||
key: 'bookRoot',
|
||
parentKey: null,
|
||
path: 'book',
|
||
name: 'book',
|
||
component: 'view/book/index.vue',
|
||
sort: 60,
|
||
hidden: false,
|
||
meta: {
|
||
title: '书籍管理',
|
||
icon: 'Reading',
|
||
keepAlive: false,
|
||
closeTab: false,
|
||
defaultMenu: false
|
||
}
|
||
},
|
||
{
|
||
key: 'bookManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookManage',
|
||
name: 'bookManage',
|
||
component: 'view/book/book/index.vue',
|
||
sort: 10,
|
||
hidden: false,
|
||
meta: { title: '书籍管理', icon: 'Notebook', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookChapterManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookChapterManage',
|
||
name: 'bookChapterManage',
|
||
component: 'view/book/chapter/index.vue',
|
||
sort: 20,
|
||
hidden: false,
|
||
meta: { title: '章节管理', icon: 'Tickets', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookAuthorManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookAuthorManage',
|
||
name: 'bookAuthorManage',
|
||
component: 'view/book/author/index.vue',
|
||
sort: 30,
|
||
hidden: false,
|
||
meta: { title: '作者管理', icon: 'User', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookSeriesManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookSeriesManage',
|
||
name: 'bookSeriesManage',
|
||
component: 'view/book/series/index.vue',
|
||
sort: 40,
|
||
hidden: false,
|
||
meta: { title: '系列管理', icon: 'Collection', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookCommentManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookCommentManage',
|
||
name: 'bookCommentManage',
|
||
component: 'view/book/comment/index.vue',
|
||
sort: 50,
|
||
hidden: false,
|
||
meta: { title: '评论管理', icon: 'ChatLineSquare', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookReadRecordManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookReadRecordManage',
|
||
name: 'bookReadRecordManage',
|
||
component: 'view/book/readRecord/index.vue',
|
||
sort: 60,
|
||
hidden: false,
|
||
meta: { title: '阅读记录管理', icon: 'View', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookFavoriteRecordManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookFavoriteRecordManage',
|
||
name: 'bookFavoriteRecordManage',
|
||
component: 'view/book/favoriteRecord/index.vue',
|
||
sort: 70,
|
||
hidden: false,
|
||
meta: { title: '收藏记录管理', icon: 'Star', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
},
|
||
{
|
||
key: 'bookCommentLikeRecordManage',
|
||
parentKey: 'bookRoot',
|
||
path: 'bookCommentLikeRecordManage',
|
||
name: 'bookCommentLikeRecordManage',
|
||
component: 'view/book/commentLikeRecord/index.vue',
|
||
sort: 80,
|
||
hidden: false,
|
||
meta: { title: '评论点赞记录管理', icon: 'Pointer', keepAlive: false, closeTab: false, defaultMenu: false }
|
||
}
|
||
]
|
||
|
||
if (!TOKEN) {
|
||
throw new Error('未找到 token,请先登录后台,或手动设置 window.__XUANZHI_TOKEN__')
|
||
}
|
||
|
||
userId = userId || await getCurrentUserId()
|
||
|
||
const createdOrFound = new Map()
|
||
|
||
for (const item of menus) {
|
||
const parentId = item.parentKey ? getMenuId(createdOrFound.get(item.parentKey)) : 0
|
||
const menu = await ensureMenu({ ...item, parentId })
|
||
createdOrFound.set(item.key, menu)
|
||
console.log(`[menu] ${item.meta.title} -> ID=${getMenuId(menu)} (${menu.component || item.component})`)
|
||
}
|
||
|
||
const result = Object.fromEntries(
|
||
[...createdOrFound.entries()].map(([key, menu]) => [key, getMenuId(menu)])
|
||
)
|
||
localStorage.setItem('xuanzhi-book-menu-ids', JSON.stringify(result))
|
||
console.log('书籍后台菜单创建/确认完成,菜单 ID 已写入 localStorage.xuanzhi-book-menu-ids:', result)
|
||
console.log('下一步:执行 02-assign-book-admin-menus-to-roles.browser-console.js 给角色分配菜单。')
|
||
|
||
async function ensureMenu(menu) {
|
||
const existed = await findMenu(menu)
|
||
if (existed) return existed
|
||
|
||
const payload = {
|
||
path: menu.path,
|
||
name: menu.name,
|
||
hidden: menu.hidden,
|
||
parentId: menu.parentId,
|
||
component: menu.component,
|
||
sort: menu.sort,
|
||
meta: menu.meta,
|
||
parameters: [],
|
||
menuBtn: []
|
||
}
|
||
|
||
await apiPost('/menu/addBaseMenu', payload)
|
||
|
||
const created = await findMenu(menu)
|
||
if (!created) {
|
||
throw new Error(`菜单已提交但未在菜单树中找到:${menu.name} / ${menu.component}`)
|
||
}
|
||
return created
|
||
}
|
||
|
||
async function findMenu(menu) {
|
||
const tree = await getBaseMenuTree()
|
||
const flat = flattenMenus(tree)
|
||
return flat.find((item) =>
|
||
item.name === menu.name ||
|
||
item.path === menu.path ||
|
||
item.component === menu.component
|
||
)
|
||
}
|
||
|
||
async function getBaseMenuTree() {
|
||
const res = await apiPost('/menu/getBaseMenuTree', {})
|
||
return res.data?.menus || []
|
||
}
|
||
|
||
async function getCurrentUserId() {
|
||
try {
|
||
const res = await apiGet('/user/getUserInfo')
|
||
return res.data?.userInfo?.ID || res.data?.userInfo?.id || ''
|
||
} catch (error) {
|
||
console.warn('读取当前用户 ID 失败,将不带 x-user-id 调用菜单接口。必要时可手动设置 window.__XUANZHI_USER_ID__。', error)
|
||
return ''
|
||
}
|
||
}
|
||
|
||
async function apiGet(path) {
|
||
return apiRequest(path, { method: 'GET' })
|
||
}
|
||
|
||
async function apiPost(path, data) {
|
||
return apiRequest(path, {
|
||
method: 'POST',
|
||
body: JSON.stringify(data)
|
||
})
|
||
}
|
||
|
||
async function apiRequest(path, options = {}) {
|
||
const res = await fetch(`${BASE_API}${path}`, {
|
||
...options,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'x-token': TOKEN,
|
||
'x-user-id': String(userId || ''),
|
||
...(options.headers || {})
|
||
}
|
||
})
|
||
const json = await res.json().catch(() => ({}))
|
||
if (!res.ok || json.code !== 0) {
|
||
throw new Error(`${path} 调用失败:${json.msg || res.statusText}`)
|
||
}
|
||
return json
|
||
}
|
||
|
||
function flattenMenus(list) {
|
||
const result = []
|
||
const walk = (items) => {
|
||
;(items || []).forEach((item) => {
|
||
result.push(item)
|
||
walk(item.children)
|
||
})
|
||
}
|
||
walk(list)
|
||
return result
|
||
}
|
||
|
||
function getMenuId(menu) {
|
||
return menu?.ID || menu?.id || menu?.menuId
|
||
}
|
||
|
||
function getCookie(name) {
|
||
return document.cookie
|
||
.split('; ')
|
||
.find((row) => row.startsWith(`${name}=`))
|
||
?.split('=')[1] || ''
|
||
}
|
||
})()
|