/** * 书籍后台管理菜单角色授权脚本 * * 使用方式: * 1. 先执行 01-create-book-admin-menus.browser-console.js。 * 2. 登录后台管理前端,打开浏览器控制台。 * 3. 如需给多个角色授权,先设置角色 ID: * window.__XUANZHI_BOOK_AUTHORITY_IDS__ = [888, 8881] * 4. 如前端接口代理不是 /api,先执行: * window.__XUANZHI_BASE_API__ = 'http://127.0.0.1:8888' * 5. 粘贴本文件全部内容并执行。 * * 说明: * - /menu/addMenuAuthority 是全量覆盖角色菜单。 * - 本脚本会先读取角色已有菜单,再合并书籍菜单,避免覆盖原有权限。 */ (async () => { const BASE_API = window.__XUANZHI_BASE_API__ || '/api' const TOKEN = window.__XUANZHI_TOKEN__ || localStorage.getItem('token') || getCookie('x-token') const AUTHORITY_IDS = window.__XUANZHI_BOOK_AUTHORITY_IDS__ || [888] const TARGET_MENU_NAMES = [ 'book', 'bookManage', 'bookChapterManage', 'bookAuthorManage', 'bookSeriesManage', 'bookCommentManage', 'bookReadRecordManage', 'bookFavoriteRecordManage', 'bookCommentLikeRecordManage' ] let userId = window.__XUANZHI_USER_ID__ || localStorage.getItem('userId') || '' if (!TOKEN) { throw new Error('未找到 token,请先登录后台,或手动设置 window.__XUANZHI_TOKEN__') } if (!Array.isArray(AUTHORITY_IDS) || AUTHORITY_IDS.length === 0) { throw new Error('请设置 window.__XUANZHI_BOOK_AUTHORITY_IDS__,例如:[888, 8881]') } userId = userId || await getCurrentUserId() const allMenus = flattenMenus(await getBaseMenuTree()) const targetMenus = TARGET_MENU_NAMES.map((name) => { const menu = allMenus.find((item) => item.name === name) if (!menu) throw new Error(`未找到菜单 ${name},请先执行创建菜单脚本。`) return menu }) for (const authorityId of AUTHORITY_IDS) { const currentMenus = await getMenuAuthority(authorityId) const merged = mergeMenusById(currentMenus, targetMenus) await apiPost('/menu/addMenuAuthority', { authorityId, menus: merged }) console.log(`[authority] ${authorityId} 已授权书籍后台菜单,合并后菜单数量:${merged.length}`) } console.log('书籍后台菜单角色授权完成。刷新页面或重新登录后可查看菜单。') async function getBaseMenuTree() { const res = await apiPost('/menu/getBaseMenuTree', {}) return res.data?.menus || [] } async function getMenuAuthority(authorityId) { const res = await apiPost('/menu/getMenuAuthority', { authorityId }) return res.data?.menus || [] } function mergeMenusById(currentMenus, targetMenus) { const map = new Map() ;[...(currentMenus || []), ...(targetMenus || [])].forEach((item) => { const id = getMenuId(item) if (!id) return map.set(Number(id), normalizeMenu(item)) }) return [...map.values()] } function normalizeMenu(item) { return { ID: getMenuId(item), path: item.path, name: item.name, hidden: item.hidden, parentId: item.parentId, component: item.component, sort: item.sort, meta: item.meta, parameters: item.parameters || [], menuBtn: item.menuBtn || [] } } 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] || '' } })()