index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <script setup lang="ts">
  2. import {
  3. ref,
  4. unref,
  5. watch,
  6. reactive,
  7. computed,
  8. nextTick,
  9. useCssModule
  10. } from "vue";
  11. import { getConfig } from "@/config";
  12. import { useRouter } from "vue-router";
  13. import panel from "../panel/index.vue";
  14. import { emitter } from "@/utils/mitt";
  15. import { resetRouter } from "@/router";
  16. import { templateRef } from "@vueuse/core";
  17. import { removeToken } from "@/utils/auth";
  18. import { routerArrays } from "@/layout/types";
  19. import { useNav } from "@/layout/hooks/useNav";
  20. import { useAppStoreHook } from "@/store/modules/app";
  21. import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
  22. import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
  23. import {
  24. useDark,
  25. debounce,
  26. useGlobal,
  27. storageLocal,
  28. storageSession
  29. } from "@pureadmin/utils";
  30. import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
  31. import dayIcon from "@/assets/svg/day.svg?component";
  32. import darkIcon from "@/assets/svg/dark.svg?component";
  33. const router = useRouter();
  34. const { device } = useNav();
  35. const { isDark } = useDark();
  36. const { isSelect } = useCssModule();
  37. const { $storage } = useGlobal<GlobalPropertiesApi>();
  38. const mixRef = templateRef<HTMLElement | null>("mixRef", null);
  39. const verticalRef = templateRef<HTMLElement | null>("verticalRef", null);
  40. const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null);
  41. const {
  42. body,
  43. dataTheme,
  44. layoutTheme,
  45. themeColors,
  46. dataThemeChange,
  47. setEpThemeColor,
  48. setLayoutThemeColor
  49. } = useDataThemeChange();
  50. /* body添加layout属性,作用于src/style/sidebar.scss */
  51. if (unref(layoutTheme)) {
  52. const layout = unref(layoutTheme).layout;
  53. const theme = unref(layoutTheme).theme;
  54. toggleTheme({
  55. scopeName: `layout-theme-${theme}`
  56. });
  57. setLayoutModel(layout);
  58. }
  59. /** 默认灵动模式 */
  60. const markValue = ref($storage.configure?.showModel ?? "smart");
  61. const logoVal = ref($storage.configure?.showLogo ?? true);
  62. const settings = reactive({
  63. greyVal: $storage.configure.grey,
  64. weakVal: $storage.configure.weak,
  65. tabsVal: $storage.configure.hideTabs,
  66. showLogo: $storage.configure.showLogo,
  67. showModel: $storage.configure.showModel,
  68. multiTagsCache: $storage.configure.multiTagsCache
  69. });
  70. const getThemeColorStyle = computed(() => {
  71. return color => {
  72. return { background: color };
  73. };
  74. });
  75. /** 当网页为暗黑模式时不显示亮白色切换选项 */
  76. const showThemeColors = computed(() => {
  77. return themeColor => {
  78. return themeColor === "light" && isDark.value ? false : true;
  79. };
  80. });
  81. function storageConfigureChange<T>(key: string, val: T): void {
  82. const storageConfigure = $storage.configure;
  83. storageConfigure[key] = val;
  84. $storage.configure = storageConfigure;
  85. }
  86. function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
  87. const targetEl = target || document.body;
  88. let { className } = targetEl;
  89. className = className.replace(clsName, "").trim();
  90. targetEl.className = flag ? `${className} ${clsName} ` : className;
  91. }
  92. /** 灰色模式设置 */
  93. const greyChange = (value): void => {
  94. toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
  95. storageConfigureChange("grey", value);
  96. };
  97. /** 色弱模式设置 */
  98. const weekChange = (value): void => {
  99. toggleClass(
  100. settings.weakVal,
  101. "html-weakness",
  102. document.querySelector("html")
  103. );
  104. storageConfigureChange("weak", value);
  105. };
  106. const tagsChange = () => {
  107. const showVal = settings.tabsVal;
  108. storageConfigureChange("hideTabs", showVal);
  109. emitter.emit("tagViewsChange", showVal as unknown as string);
  110. };
  111. const multiTagsCacheChange = () => {
  112. const multiTagsCache = settings.multiTagsCache;
  113. storageConfigureChange("multiTagsCache", multiTagsCache);
  114. useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
  115. };
  116. /** 清空缓存并返回登录页 */
  117. function onReset() {
  118. removeToken();
  119. storageLocal.clear();
  120. storageSession.clear();
  121. const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
  122. useAppStoreHook().setLayout(Layout);
  123. setEpThemeColor(EpThemeColor);
  124. useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
  125. toggleClass(Grey, "html-grey", document.querySelector("html"));
  126. toggleClass(Weak, "html-weakness", document.querySelector("html"));
  127. router.push("/login");
  128. useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
  129. resetRouter();
  130. }
  131. function onChange(label) {
  132. storageConfigureChange("showModel", label);
  133. emitter.emit("tagViewsShowModel", label);
  134. }
  135. /** 侧边栏Logo */
  136. function logoChange() {
  137. unref(logoVal)
  138. ? storageConfigureChange("showLogo", true)
  139. : storageConfigureChange("showLogo", false);
  140. emitter.emit("logoChange", unref(logoVal));
  141. }
  142. function setFalse(Doms): any {
  143. Doms.forEach(v => {
  144. toggleClass(false, isSelect, unref(v));
  145. });
  146. }
  147. watch($storage, ({ layout }) => {
  148. /* 设置wangeditorV5主题色 */
  149. body.style.setProperty("--w-e-toolbar-active-color", layout["epThemeColor"]);
  150. switch (layout["layout"]) {
  151. case "vertical":
  152. toggleClass(true, isSelect, unref(verticalRef));
  153. debounce(setFalse([horizontalRef]), 50);
  154. debounce(setFalse([mixRef]), 50);
  155. break;
  156. case "horizontal":
  157. toggleClass(true, isSelect, unref(horizontalRef));
  158. debounce(setFalse([verticalRef]), 50);
  159. debounce(setFalse([mixRef]), 50);
  160. break;
  161. case "mix":
  162. toggleClass(true, isSelect, unref(mixRef));
  163. debounce(setFalse([verticalRef]), 50);
  164. debounce(setFalse([horizontalRef]), 50);
  165. break;
  166. }
  167. });
  168. /** 主题色 激活选择项 */
  169. const getThemeColor = computed(() => {
  170. return current => {
  171. if (
  172. current === layoutTheme.value.theme &&
  173. layoutTheme.value.theme !== "light"
  174. ) {
  175. return "#fff";
  176. } else if (
  177. current === layoutTheme.value.theme &&
  178. layoutTheme.value.theme === "light"
  179. ) {
  180. return "#1d2b45";
  181. } else {
  182. return "transparent";
  183. }
  184. };
  185. });
  186. /** 设置导航模式 */
  187. function setLayoutModel(layout: string) {
  188. layoutTheme.value.layout = layout;
  189. window.document.body.setAttribute("layout", layout);
  190. $storage.layout = {
  191. layout,
  192. theme: layoutTheme.value.theme,
  193. darkMode: $storage.layout?.darkMode,
  194. sidebarStatus: $storage.layout?.sidebarStatus,
  195. epThemeColor: $storage.layout?.epThemeColor
  196. };
  197. useAppStoreHook().setLayout(layout);
  198. }
  199. /* 初始化项目配置 */
  200. nextTick(() => {
  201. settings.greyVal &&
  202. document.querySelector("html")?.setAttribute("class", "html-grey");
  203. settings.weakVal &&
  204. document.querySelector("html")?.setAttribute("class", "html-weakness");
  205. settings.tabsVal && tagsChange();
  206. dataThemeChange();
  207. });
  208. </script>
  209. <template>
  210. <panel>
  211. <el-divider>主题</el-divider>
  212. <el-switch
  213. v-model="dataTheme"
  214. inline-prompt
  215. class="pure-datatheme"
  216. :active-icon="dayIcon"
  217. :inactive-icon="darkIcon"
  218. @change="dataThemeChange"
  219. />
  220. <el-divider>导航栏模式</el-divider>
  221. <ul class="pure-theme">
  222. <el-tooltip class="item" content="左侧模式" placement="bottom">
  223. <li
  224. :class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
  225. ref="verticalRef"
  226. @click="setLayoutModel('vertical')"
  227. >
  228. <div />
  229. <div />
  230. </li>
  231. </el-tooltip>
  232. <el-tooltip
  233. v-if="device !== 'mobile'"
  234. class="item"
  235. content="顶部模式"
  236. placement="bottom"
  237. >
  238. <li
  239. :class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
  240. ref="horizontalRef"
  241. @click="setLayoutModel('horizontal')"
  242. >
  243. <div />
  244. <div />
  245. </li>
  246. </el-tooltip>
  247. <el-tooltip
  248. v-if="device !== 'mobile'"
  249. class="item"
  250. content="混合模式"
  251. placement="bottom"
  252. >
  253. <li
  254. :class="layoutTheme.layout === 'mix' ? $style.isSelect : ''"
  255. ref="mixRef"
  256. @click="setLayoutModel('mix')"
  257. >
  258. <div />
  259. <div />
  260. </li>
  261. </el-tooltip>
  262. </ul>
  263. <el-divider>主题色</el-divider>
  264. <ul class="theme-color">
  265. <li
  266. v-for="(item, index) in themeColors"
  267. :key="index"
  268. v-show="showThemeColors(item.themeColor)"
  269. :style="getThemeColorStyle(item.color)"
  270. @click="setLayoutThemeColor(item.themeColor)"
  271. >
  272. <el-icon
  273. style="margin: 0.1em 0.1em 0 0"
  274. :size="17"
  275. :color="getThemeColor(item.themeColor)"
  276. >
  277. <IconifyIconOffline icon="check" />
  278. </el-icon>
  279. </li>
  280. </ul>
  281. <el-divider>界面显示</el-divider>
  282. <ul class="setting">
  283. <li>
  284. <span class="dark:text-white">灰色模式</span>
  285. <el-switch
  286. v-model="settings.greyVal"
  287. inline-prompt
  288. inactive-color="#a6a6a6"
  289. active-text="开"
  290. inactive-text="关"
  291. @change="greyChange"
  292. />
  293. </li>
  294. <li>
  295. <span class="dark:text-white">色弱模式</span>
  296. <el-switch
  297. v-model="settings.weakVal"
  298. inline-prompt
  299. inactive-color="#a6a6a6"
  300. active-text="开"
  301. inactive-text="关"
  302. @change="weekChange"
  303. />
  304. </li>
  305. <li>
  306. <span class="dark:text-white">隐藏标签页</span>
  307. <el-switch
  308. v-model="settings.tabsVal"
  309. inline-prompt
  310. inactive-color="#a6a6a6"
  311. active-text="开"
  312. inactive-text="关"
  313. @change="tagsChange"
  314. />
  315. </li>
  316. <li>
  317. <span class="dark:text-white">侧边栏Logo</span>
  318. <el-switch
  319. v-model="logoVal"
  320. inline-prompt
  321. :active-value="true"
  322. :inactive-value="false"
  323. inactive-color="#a6a6a6"
  324. active-text="开"
  325. inactive-text="关"
  326. @change="logoChange"
  327. />
  328. </li>
  329. <li>
  330. <span class="dark:text-white">标签页持久化</span>
  331. <el-switch
  332. v-model="settings.multiTagsCache"
  333. inline-prompt
  334. inactive-color="#a6a6a6"
  335. active-text="开"
  336. inactive-text="关"
  337. @change="multiTagsCacheChange"
  338. />
  339. </li>
  340. <li>
  341. <span class="dark:text-white">标签风格</span>
  342. <el-radio-group v-model="markValue" size="small" @change="onChange">
  343. <el-radio label="card">卡片</el-radio>
  344. <el-radio label="smart">灵动</el-radio>
  345. </el-radio-group>
  346. </li>
  347. </ul>
  348. <el-divider />
  349. <el-button
  350. type="danger"
  351. style="width: 90%; margin: 24px 15px"
  352. @click="onReset"
  353. >
  354. <IconifyIconOffline
  355. icon="fa-sign-out"
  356. width="15"
  357. height="15"
  358. style="margin-right: 4px"
  359. />
  360. 清空缓存并返回登录页
  361. </el-button>
  362. </panel>
  363. </template>
  364. <style scoped module>
  365. .isSelect {
  366. border: 2px solid var(--el-color-primary);
  367. }
  368. </style>
  369. <style lang="scss" scoped>
  370. .setting {
  371. width: 100%;
  372. li {
  373. display: flex;
  374. justify-content: space-between;
  375. align-items: center;
  376. margin: 25px;
  377. }
  378. }
  379. :deep(.el-divider__text) {
  380. font-size: 16px;
  381. font-weight: 700;
  382. }
  383. .pure-datatheme {
  384. width: 100%;
  385. height: 50px;
  386. text-align: center;
  387. display: block;
  388. padding-top: 25px;
  389. }
  390. .pure-theme {
  391. margin-top: 25px;
  392. width: 100%;
  393. height: 50px;
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. li {
  398. width: 18%;
  399. height: 45px;
  400. background: #f0f2f5;
  401. position: relative;
  402. overflow: hidden;
  403. cursor: pointer;
  404. border-radius: 4px;
  405. box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
  406. &:nth-child(1) {
  407. div {
  408. &:nth-child(1) {
  409. width: 30%;
  410. height: 100%;
  411. background: #1b2a47;
  412. }
  413. &:nth-child(2) {
  414. width: 70%;
  415. height: 30%;
  416. top: 0;
  417. right: 0;
  418. background: #fff;
  419. box-shadow: 0 0 1px #888;
  420. position: absolute;
  421. }
  422. }
  423. }
  424. &:nth-child(2) {
  425. div {
  426. &:nth-child(1) {
  427. width: 100%;
  428. height: 30%;
  429. background: #1b2a47;
  430. box-shadow: 0 0 1px #888;
  431. }
  432. }
  433. }
  434. &:nth-child(3) {
  435. div {
  436. &:nth-child(1) {
  437. width: 100%;
  438. height: 30%;
  439. background: #1b2a47;
  440. box-shadow: 0 0 1px #888;
  441. }
  442. &:nth-child(2) {
  443. width: 30%;
  444. height: 70%;
  445. bottom: 0;
  446. left: 0;
  447. background: #fff;
  448. box-shadow: 0 0 1px #888;
  449. position: absolute;
  450. }
  451. }
  452. }
  453. }
  454. }
  455. .theme-color {
  456. width: 100%;
  457. height: 40px;
  458. margin-top: 20px;
  459. display: flex;
  460. justify-content: center;
  461. li {
  462. float: left;
  463. width: 20px;
  464. height: 20px;
  465. margin-top: 8px;
  466. margin-right: 8px;
  467. font-weight: 700;
  468. text-align: center;
  469. border-radius: 2px;
  470. cursor: pointer;
  471. &:nth-child(2) {
  472. border: 1px solid #ddd;
  473. }
  474. }
  475. }
  476. </style>