deepchart后台管理系统
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.

472 lines
13 KiB

3 weeks ago
  1. <!-- @format -->
  2. <template>
  3. <el-container style="min-height: 100vh">
  4. <el-aside class="sidebar" v-if="!$route.meta.hiddenSidebar">
  5. <!-- 侧边栏头部 -->
  6. <div class="sidebar-header">
  7. <img src="../assets/images/deepChart.png" class="sidebar-logo" />
  8. <span class="sidebar-title">DeepChart</span>
  9. </div>
  10. <!-- 侧边栏菜单 -->
  11. <el-menu class="sidebar-menu" background-color="transparent" router :default-active="lastActivePath">
  12. <!-- 第一层循环遍历父路由 (: 用户权限管理活动管理) -->
  13. <el-sub-menu v-for="parentRoute in filteredSidebarRoutes" :key="parentRoute.name" :index="`/${parentRoute.path}`">
  14. <!-- 父目录 -->
  15. <template #title>
  16. <el-icon>
  17. <component :is="parentRoute.meta.icon" />
  18. </el-icon>
  19. <span class="sidebar-parent-text">{{ parentRoute.meta.title }}</span>
  20. </template>
  21. <!-- 第二层循环遍历子路由 -->
  22. <template v-for="childRoute in parentRoute.filteredChildren" :key="childRoute.name">
  23. <!-- 如果有孙子菜单新年幸运签渲染为子目录 el-sub-menu -->
  24. <el-sub-menu v-if="childRoute.children && childRoute.children.length > 0" :index="`/${parentRoute.path}/${childRoute.path}`">
  25. <template #title>
  26. <el-icon class="sidebar-child-icon" />
  27. <span class="sidebar-child-text">{{ childRoute.meta.title }}</span>
  28. </template>
  29. <!-- 第三层循环遍历孙子路由 (: 历史记录内容配置) -->
  30. <el-menu-item v-for="grandChild in childRoute.children" :key="grandChild.name" :index="`/${parentRoute.path}/${childRoute.path}/${grandChild.path}`" class="sidebar-grandchild-container">
  31. <span class="sidebar-grandchild-text">{{ grandChild.meta.title }}</span>
  32. </el-menu-item>
  33. </el-sub-menu>
  34. <!-- 如果没有子菜单行情期限渲染为点击项 el-menu-item -->
  35. <el-menu-item v-else :index="`/${parentRoute.path}/${childRoute.path}`" class="sidebar-child-container">
  36. <el-icon class="sidebar-child-icon" />
  37. <span class="sidebar-child-text">{{ childRoute.meta.title }}</span>
  38. </el-menu-item>
  39. </template>
  40. </el-sub-menu>
  41. </el-menu>
  42. <div class="sidebar-logout" @click="handleLogout">
  43. <el-icon style="bottom: -3px"><SwitchButton /></el-icon>
  44. 退出登录
  45. </div>
  46. <div class="sidebar-set" @click="handleSet" v-if="permission == '2'">
  47. <el-icon style="bottom: -3px"><Setting /></el-icon>
  48. 设置
  49. </div>
  50. </el-aside>
  51. <!-- 首页刷新时间弹窗 -->
  52. <el-dialog v-model="setValue" title="设置" width="500">
  53. <div class="refresh-time-container">
  54. <el-button type="danger">首页刷新时间</el-button>
  55. <el-form-item label="刷新时间" style="margin-top: 20px; margin-bottom: 40px">
  56. <el-input-number v-model="refreshTime" :step="1" placeholder="请输入刷新时间" style="width: 200px">
  57. <template #suffix>
  58. <span>s</span>
  59. </template>
  60. </el-input-number>
  61. </el-form-item>
  62. <el-button type="danger">Android下载链接配置</el-button>
  63. <el-form-item label="编辑链接" style="margin-top: 20px; margin-bottom: 40px">
  64. <el-input v-model="Androidurl" style="width: 300px" placeholder="请输入最新Android下载链接" />
  65. </el-form-item>
  66. </div>
  67. <template #footer>
  68. <div class="dialog-footer">
  69. <el-button @click="setValue = false">取消</el-button>
  70. <el-button type="danger" @click="setEnv">确认修改</el-button>
  71. </div>
  72. </template>
  73. </el-dialog>
  74. <!-- 主页面 -->
  75. <el-main class="main-content">
  76. <router-view />
  77. </el-main>
  78. </el-container>
  79. </template>
  80. <script setup>
  81. import { computed, ref, watch, onMounted } from "vue";
  82. import { useRouter, useRoute } from "vue-router";
  83. import { ElMessage } from "element-plus";
  84. import { getEnvApi, setEnvApi } from "../api/userPermissions";
  85. import { usePermissionStore } from "../store/permission";
  86. const permissionStore = usePermissionStore();
  87. const router = useRouter();
  88. const route = useRoute();
  89. // token
  90. const token = localStorage.getItem("token");
  91. //permission
  92. const permission = ref("-1");
  93. // 递归收集父路由
  94. const collectParentRoutes = (routes) => {
  95. let parentRoutes = [];
  96. routes.forEach((route) => {
  97. if (route.meta?.isParentNav === true && route.meta?.showSidebar === true) {
  98. parentRoutes.push(route);
  99. }
  100. if (route.children && route.children.length > 0) {
  101. parentRoutes = [...parentRoutes, ...collectParentRoutes(route.children)];
  102. }
  103. });
  104. return parentRoutes;
  105. };
  106. const getPermission = async () => {
  107. const result = await getPermissionApi({ token: token });
  108. console.log("result", result);
  109. permission.value = result.permission_level;
  110. };
  111. // 过滤侧边栏路由
  112. const filteredSidebarRoutes = computed(() => {
  113. const allParentRoutes = collectParentRoutes(router.options.routes);
  114. return allParentRoutes
  115. .map((parentRoute) => {
  116. // 先获取 children,如果为空则设为 []
  117. const rawChildren = parentRoute.children || [];
  118. const filteredChildren = rawChildren
  119. .map((childRoute) => {
  120. // 浅拷贝 childRoute,避免直接修改原始路由对象
  121. const newChild = { ...childRoute };
  122. if (newChild.children && newChild.children.length > 0) {
  123. newChild.children = newChild.children.filter(
  124. (grandChild) => grandChild.meta?.showSidebar === true
  125. );
  126. }
  127. return newChild;
  128. })
  129. .filter((childRoute) => {
  130. // 必须标记为显示侧边栏
  131. if (childRoute.meta?.showSidebar !== true) return false;
  132. // 权限判断
  133. if (permission.value == "2") {
  134. // 权限为2可以看到全部
  135. return true;
  136. } else if (permission.value == "1") {
  137. // 权限为1只可以看到查看被邀请用户
  138. return childRoute.name === "invitedLook";
  139. }
  140. // 默认情况(如未登录或无权限字段),根据实际需求处理,这里暂时隐藏
  141. return false;
  142. });
  143. return { ...parentRoute, filteredChildren };
  144. })
  145. .filter((parentRoute) => parentRoute.filteredChildren.length > 0);
  146. });
  147. // 计算所有侧边栏有效菜单集合
  148. const validMenuIndexes = computed(() => {
  149. const indexes = [];
  150. filteredSidebarRoutes.value.forEach((parentRoute) => {
  151. // 收集父菜单
  152. indexes.push(`/${parentRoute.path}`);
  153. // 收集子菜单
  154. parentRoute.filteredChildren.forEach((childRoute) => {
  155. indexes.push(`/${parentRoute.path}/${childRoute.path}`);
  156. // 收集孙子菜单
  157. if (childRoute.children && childRoute.children.length > 0) {
  158. childRoute.children.forEach((grandChild) => {
  159. // 收集三级菜单路径 (确保拼接正确)
  160. indexes.push(`/${parentRoute.path}/${childRoute.path}/${grandChild.path}`);
  161. });
  162. }
  163. });
  164. });
  165. return indexes;
  166. });
  167. // 存储最后一次选中的「有效侧边栏路径」
  168. const lastActivePath = ref("");
  169. // 初始化+监听路由变化,更新最后有效路径
  170. watch(
  171. [() => route.path, validMenuIndexes],
  172. ([newPath, newIndexes]) => {
  173. // 如果新路由是侧边栏有效路径,更新,否则不跟新
  174. if (newIndexes.includes(newPath)) {
  175. lastActivePath.value = newPath;
  176. }
  177. },
  178. { immediate: true } // 初始化时立即执行一次
  179. );
  180. // 退出登录
  181. const handleLogout = () => {
  182. try {
  183. localStorage.removeItem("token");
  184. router.push("/login");
  185. ElMessage.success("退出登录成功");
  186. } catch (error) {
  187. ElMessage.error("退出登录失败,请重试");
  188. }
  189. };
  190. // 设置弹窗开关
  191. const setValue = ref(false);
  192. // 首页刷新时间
  193. const refreshTime = ref("");
  194. // 安卓最新安装包
  195. const Androidurl = ref("");
  196. // 设置按钮
  197. const handleSet = async () => {
  198. const data1 = await getEnvApi({
  199. token: token,
  200. key: "SYNC_INTERVAL",
  201. });
  202. const data2 = await getEnvApi({
  203. token: token,
  204. key: "DOWNLOAD_URL",
  205. });
  206. refreshTime.value = data1;
  207. Androidurl.value = data2;
  208. setValue.value = true;
  209. };
  210. // 确认修改按钮
  211. const setEnv = async () => {
  212. try {
  213. await setEnvApi({
  214. token: token,
  215. key: "SYNC_INTERVAL",
  216. value: refreshTime.value,
  217. });
  218. await setEnvApi({
  219. token: token,
  220. key: "DOWNLOAD_URL",
  221. value: Androidurl.value,
  222. });
  223. ElMessage.success("修改成功");
  224. setValue.value = false;
  225. } catch (error) {
  226. ElMessage.error("修改失败");
  227. setValue.value = false;
  228. }
  229. };
  230. onMounted(async () => {
  231. permission.value = await permissionStore.getPermission();
  232. });
  233. </script>
  234. <style scoped>
  235. /* 侧边栏核心样式 */
  236. .sidebar {
  237. position: fixed;
  238. left: 24px;
  239. top: 10px;
  240. width: 336px;
  241. height: calc(100vh - 20px);
  242. flex-shrink: 0;
  243. border-radius: 8px;
  244. background: #fee6e6;
  245. box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.25);
  246. overflow: hidden;
  247. position: relative;
  248. }
  249. /* 侧边栏头部样式 */
  250. .sidebar-header {
  251. position: absolute;
  252. left: 45px;
  253. top: 59px;
  254. display: flex;
  255. align-items: center;
  256. }
  257. .sidebar-logo {
  258. height: 40px;
  259. width: auto;
  260. object-fit: contain;
  261. }
  262. .sidebar-title {
  263. margin-left: 10px;
  264. color: #000000;
  265. font-family: "PingFang SC", sans-serif;
  266. font-size: 32px;
  267. font-style: normal;
  268. font-weight: 700;
  269. line-height: 33.1px;
  270. white-space: nowrap;
  271. }
  272. /* 侧边栏菜单容器 */
  273. .sidebar-menu {
  274. margin-left: 20px;
  275. margin-top: 169px;
  276. width: calc(100% - 20px);
  277. border-right: none;
  278. height: 70%;
  279. overflow-y: auto;
  280. overflow-x: hidden;
  281. }
  282. /* 主内容区样式 */
  283. .main-content {
  284. padding: 20px;
  285. background-color: #fee6e6;
  286. height: calc(100vh - 20px);
  287. margin-left: 50px;
  288. margin-right: 24px;
  289. margin-top: 10px;
  290. box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.25);
  291. }
  292. /* 父目录文字样式 */
  293. .sidebar-parent-text {
  294. height: 33.1px;
  295. flex: 1 0 0;
  296. overflow: hidden;
  297. color: #1f0303;
  298. text-overflow: ellipsis;
  299. white-space: nowrap;
  300. font-family: "PingFang SC", sans-serif;
  301. font-size: 21.06px;
  302. font-style: normal;
  303. font-weight: 700;
  304. line-height: 33.1px;
  305. display: inline-flex;
  306. align-items: center;
  307. }
  308. /* 子目录核心样式 */
  309. .sidebar-child-container {
  310. width: 300px !important;
  311. height: 60px !important;
  312. margin-left: 2px !important;
  313. background: #fee6e6 !important;
  314. position: relative;
  315. padding: 0 !important;
  316. border-radius: 6.02px !important;
  317. box-sizing: border-box !important;
  318. }
  319. /* 子目录图标样式 */
  320. .sidebar-child-icon {
  321. position: absolute;
  322. left: 48px;
  323. top: 50%;
  324. transform: translateY(-50%);
  325. font-size: 18px !important;
  326. color: inherit;
  327. }
  328. /* 子目录文字样式(未选中) */
  329. .sidebar-child-text {
  330. position: absolute;
  331. left: 58px;
  332. top: 20px;
  333. height: 33.1px;
  334. overflow: hidden;
  335. color: #03071f;
  336. text-overflow: ellipsis;
  337. white-space: nowrap;
  338. font-family: "PingFang SC", sans-serif;
  339. font-size: 18px;
  340. font-style: normal;
  341. font-weight: 400;
  342. line-height: 22px;
  343. }
  344. .sidebar-grandchild-text {
  345. position: absolute;
  346. left: 78px;
  347. top: 15px;
  348. height: 33px;
  349. overflow: hidden;
  350. color: #03071f;
  351. text-overflow: ellipsis;
  352. white-space: nowrap;
  353. font-family: "PingFang SC", sans-serif;
  354. font-size: 16px;
  355. font-style: normal;
  356. font-weight: 400;
  357. line-height: 22px;
  358. }
  359. /* 子目录选中态样式 */
  360. .sidebar-child-container.is-active,.sidebar-grandchild-container.is-active {
  361. background: #ffffff !important; /* 选中后容器变为白色 */
  362. }
  363. /* 选中态文字样式 */
  364. .sidebar-child-container.is-active .sidebar-child-text,.sidebar-grandchild-container.is-active .sidebar-grandchild-text {
  365. color: #ff0000 !important; /* 选中后文字红色 */
  366. font-weight: 500;
  367. }
  368. /* 覆盖Element Plus默认样式 */
  369. .el-sub-menu__title {
  370. height: 33.1px !important;
  371. line-height: 33.1px !important;
  372. padding: 0 !important;
  373. }
  374. .el-menu-item {
  375. border: none !important;
  376. }
  377. .el-menu--vertical .el-menu-item {
  378. width: 300px !important;
  379. }
  380. /* 退出登录 */
  381. .sidebar-logout {
  382. position: absolute;
  383. bottom: 30px;
  384. left: 10%;
  385. color: #1f0303;
  386. font-family: "PingFang SC", sans-serif;
  387. font-size: 21.06px;
  388. font-style: normal;
  389. font-weight: 700;
  390. line-height: 33.1px;
  391. cursor: pointer;
  392. user-select: none;
  393. transition: color 0.2s ease;
  394. }
  395. /* 退出登录hover效果 */
  396. .sidebar-logout:hover {
  397. color: #ff0000;
  398. }
  399. /* 设置 */
  400. .sidebar-set {
  401. position: absolute;
  402. bottom: 30px;
  403. left: 60%;
  404. color: #1f0303;
  405. font-family: "PingFang SC", sans-serif;
  406. font-size: 21.06px;
  407. font-style: normal;
  408. font-weight: 700;
  409. line-height: 33.1px;
  410. cursor: pointer;
  411. user-select: none;
  412. transition: color 0.2s ease;
  413. }
  414. /* 设置hover效果 */
  415. .sidebar-set:hover {
  416. color: #ff0000;
  417. }
  418. </style>