From 8eb583397d28585ad44969c06cb7de74c9d8224d Mon Sep 17 00:00:00 2001 From: xiangheng <11675084@qq.com> Date: Fri, 24 Nov 2023 16:46:30 +0800 Subject: [PATCH] init --- .gitignore | 22 + LICENSE | 21 + README.md | 54 ++ admin/.editorconfig | 14 + admin/.env.development.example | 4 + admin/.env.production.example | 4 + admin/.eslintrc.cjs | 43 + admin/.gitignore | 36 + admin/.prettierrc | 11 + admin/.vscode/extensions.json | 3 + admin/.vscode/settings.json | 11 + admin/README.md | 46 + admin/global.d.ts | 1 + admin/index.html | 66 ++ admin/package.json | 63 ++ admin/postcss.config.js | 6 + admin/scripts/build.mjs | 37 + admin/src/App.vue | 59 ++ admin/src/api/app.ts | 11 + admin/src/api/article.ts | 69 ++ admin/src/api/article_collect.ts | 26 + admin/src/api/channel/h5.ts | 11 + admin/src/api/channel/weapp.ts | 11 + admin/src/api/channel/wx_dev.ts | 11 + admin/src/api/channel/wx_oa.ts | 112 +++ admin/src/api/consumer.ts | 16 + admin/src/api/decoration.ts | 26 + admin/src/api/file.ts | 39 + admin/src/api/message.ts | 31 + admin/src/api/org/department.ts | 26 + admin/src/api/org/post.ts | 30 + admin/src/api/perms/admin.ts | 31 + admin/src/api/perms/menu.ts | 26 + admin/src/api/perms/role.ts | 29 + admin/src/api/setting/dict.ts | 61 ++ admin/src/api/setting/search.ts | 27 + admin/src/api/setting/storage.ts | 21 + admin/src/api/setting/system.ts | 16 + admin/src/api/setting/user.ts | 42 + admin/src/api/setting/website.ts | 27 + admin/src/api/tools/code.ts | 66 ++ admin/src/api/user.ts | 27 + admin/src/assets/icons/Androidfanhui.svg | 1 + admin/src/assets/icons/KMSguanli.svg | 1 + admin/src/assets/icons/KTVyuding.svg | 1 + admin/src/assets/icons/a-tixingdengpao.svg | 1 + admin/src/assets/icons/anquan.svg | 1 + admin/src/assets/icons/anquan_mian.svg | 1 + admin/src/assets/icons/anquan_mian1.svg | 1 + admin/src/assets/icons/banxing_mian.svg | 1 + admin/src/assets/icons/baoxian.svg | 1 + admin/src/assets/icons/bendishenghuodaxue.svg | 1 + admin/src/assets/icons/bianji.svg | 1 + admin/src/assets/icons/biaoqing.svg | 1 + admin/src/assets/icons/bukejian.svg | 1 + admin/src/assets/icons/caipinguanli.svg | 1 + admin/src/assets/icons/caiwu.svg | 1 + admin/src/assets/icons/caiwu_jifen.svg | 1 + admin/src/assets/icons/caiwu_tixian.svg | 1 + admin/src/assets/icons/canyinfuwu.svg | 1 + admin/src/assets/icons/carryout.svg | 1 + admin/src/assets/icons/chexiao.svg | 1 + admin/src/assets/icons/chihuohongbao.svg | 1 + admin/src/assets/icons/chuangyiwuliao.svg | 1 + admin/src/assets/icons/close.svg | 1 + admin/src/assets/icons/daiyunying.svg | 1 + admin/src/assets/icons/danwei.svg | 1 + admin/src/assets/icons/danxuankuang.svg | 1 + admin/src/assets/icons/danxuanxuanzhong.svg | 1 + admin/src/assets/icons/dayin.svg | 1 + admin/src/assets/icons/dayin_mian.svg | 1 + admin/src/assets/icons/del.svg | 1 + admin/src/assets/icons/diancanshezhi.svg | 1 + admin/src/assets/icons/dianhua.svg | 1 + admin/src/assets/icons/dianhua_mian.svg | 1 + admin/src/assets/icons/dianpu_fengge.svg | 1 + admin/src/assets/icons/dianputuijian.svg | 1 + admin/src/assets/icons/dianzifapiao.svg | 1 + admin/src/assets/icons/dingcan.svg | 1 + admin/src/assets/icons/dingdan.svg | 1 + admin/src/assets/icons/dingdan1.svg | 1 + admin/src/assets/icons/dingdan_mian.svg | 1 + admin/src/assets/icons/dingwei.svg | 1 + admin/src/assets/icons/dingwei_mian.svg | 1 + admin/src/assets/icons/ditu.svg | 1 + admin/src/assets/icons/ditu_mian.svg | 1 + admin/src/assets/icons/duizhang.svg | 1 + admin/src/assets/icons/elemo.svg | 1 + admin/src/assets/icons/ezhanggui.svg | 1 + admin/src/assets/icons/falvfuwubaoxiaohei.svg | 1 + admin/src/assets/icons/fengniaopaotui.svg | 1 + admin/src/assets/icons/fenxiang.svg | 1 + admin/src/assets/icons/fukuan.svg | 1 + admin/src/assets/icons/fukuan_mian.svg | 1 + admin/src/assets/icons/fullscreen-exit.svg | 1 + admin/src/assets/icons/fullscreen.svg | 1 + admin/src/assets/icons/fuwushichang.svg | 1 + admin/src/assets/icons/fuzhi.svg | 1 + admin/src/assets/icons/gaode.svg | 1 + admin/src/assets/icons/gengduo.svg | 1 + admin/src/assets/icons/gengduoandroid.svg | 1 + admin/src/assets/icons/gift.svg | 1 + admin/src/assets/icons/gongyingshang.svg | 1 + admin/src/assets/icons/goods.svg | 1 + admin/src/assets/icons/gou.svg | 1 + admin/src/assets/icons/gouwuche.svg | 1 + admin/src/assets/icons/gouxuan.svg | 1 + admin/src/assets/icons/gouxuan_mian.svg | 1 + admin/src/assets/icons/guanbi.svg | 1 + admin/src/assets/icons/guanli.svg | 1 + admin/src/assets/icons/guanli_mian.svg | 1 + admin/src/assets/icons/gukefapiao.svg | 1 + admin/src/assets/icons/haibaosheji.svg | 1 + admin/src/assets/icons/heshoujilu.svg | 1 + admin/src/assets/icons/heshoujilu1.svg | 1 + admin/src/assets/icons/hexiao_order.svg | 1 + admin/src/assets/icons/hide-2.svg | 1 + admin/src/assets/icons/hide.svg | 1 + admin/src/assets/icons/hongbao.svg | 1 + admin/src/assets/icons/huiche.svg | 1 + admin/src/assets/icons/huiyuanyingxiao.svg | 1 + admin/src/assets/icons/huodongbaoming.svg | 1 + admin/src/assets/icons/huodongguanli.svg | 1 + admin/src/assets/icons/huodongzhongxin.svg | 1 + admin/src/assets/icons/huojian.svg | 1 + admin/src/assets/icons/huojian_mian.svg | 1 + admin/src/assets/icons/huolala.svg | 1 + admin/src/assets/icons/iOSfanhui.svg | 1 + admin/src/assets/icons/jia.svg | 1 + admin/src/assets/icons/jia_mian.svg | 1 + admin/src/assets/icons/jian.svg | 1 + admin/src/assets/icons/jian_mian.svg | 1 + admin/src/assets/icons/jianpan.svg | 1 + admin/src/assets/icons/jianpanshanchu.svg | 1 + admin/src/assets/icons/jianshao.svg | 1 + admin/src/assets/icons/jiaopeiwangputong.svg | 1 + admin/src/assets/icons/jiaoyi.svg | 1 + admin/src/assets/icons/jiedan.svg | 1 + admin/src/assets/icons/jiekuan.svg | 1 + admin/src/assets/icons/jingshi.svg | 1 + admin/src/assets/icons/jingshi_mian.svg | 1 + admin/src/assets/icons/jingshi_mian1.svg | 1 + admin/src/assets/icons/jingyin.svg | 1 + admin/src/assets/icons/jingyin_mian.svg | 1 + admin/src/assets/icons/jingying.svg | 1 + admin/src/assets/icons/jingying_mian.svg | 1 + admin/src/assets/icons/jingyinggonglve.svg | 1 + admin/src/assets/icons/jingzhunyingxiao.svg | 1 + admin/src/assets/icons/jinhuo.svg | 1 + admin/src/assets/icons/kaitongwaimai.svg | 1 + admin/src/assets/icons/kanjia.svg | 1 + admin/src/assets/icons/kefu.svg | 1 + admin/src/assets/icons/kejian.svg | 1 + admin/src/assets/icons/kejian_mian.svg | 1 + admin/src/assets/icons/keziyuyue.svg | 1 + admin/src/assets/icons/kezizhongxin.svg | 1 + admin/src/assets/icons/koubei.svg | 1 + admin/src/assets/icons/kuaijiehuifu.svg | 1 + admin/src/assets/icons/ladu_mian.svg | 1 + admin/src/assets/icons/lanyadingwei.svg | 1 + admin/src/assets/icons/list-2.svg | 1 + admin/src/assets/icons/mendiandongtai.svg | 1 + admin/src/assets/icons/mishiyuding.svg | 1 + admin/src/assets/icons/mishiyuding1.svg | 1 + admin/src/assets/icons/notice_buyer.svg | 1 + admin/src/assets/icons/open.svg | 1 + admin/src/assets/icons/paiduiquhao.svg | 1 + admin/src/assets/icons/paimai.svg | 1 + admin/src/assets/icons/pingjia.svg | 1 + admin/src/assets/icons/pingtaifapiao.svg | 1 + admin/src/assets/icons/pinpai.svg | 1 + admin/src/assets/icons/qianbao.svg | 1 + admin/src/assets/icons/qianbao_mian.svg | 1 + admin/src/assets/icons/qiehuan.svg | 1 + admin/src/assets/icons/qingchu.svg | 1 + admin/src/assets/icons/qingchu_mian.svg | 1 + admin/src/assets/icons/qishoupeisong.svg | 1 + admin/src/assets/icons/qiyedingcan.svg | 1 + admin/src/assets/icons/qiyedingcan1.svg | 1 + admin/src/assets/icons/quanbu.svg | 1 + admin/src/assets/icons/quanping.svg | 1 + admin/src/assets/icons/qudao.svg | 1 + admin/src/assets/icons/qudao_xiaochengxu.svg | 1 + admin/src/assets/icons/rencaizhaopin.svg | 1 + admin/src/assets/icons/rili.svg | 1 + admin/src/assets/icons/rili2.svg | 1 + admin/src/assets/icons/rizhi.svg | 1 + admin/src/assets/icons/saoma.svg | 1 + admin/src/assets/icons/set_pay.svg | 1 + admin/src/assets/icons/set_peisong.svg | 1 + admin/src/assets/icons/set_user.svg | 1 + admin/src/assets/icons/set_weihu.svg | 1 + admin/src/assets/icons/shanchu.svg | 1 + admin/src/assets/icons/shanchu_mian.svg | 1 + admin/src/assets/icons/shangchuan.svg | 1 + admin/src/assets/icons/shangchuanzhaopian.svg | 1 + admin/src/assets/icons/shangpinguanli.svg | 1 + admin/src/assets/icons/shangpinzhushou.svg | 1 + admin/src/assets/icons/shangpuyuding.svg | 1 + admin/src/assets/icons/shebeiguanli.svg | 1 + admin/src/assets/icons/shengfuwangputong.svg | 1 + admin/src/assets/icons/shengyin.svg | 1 + admin/src/assets/icons/shengyin_mian.svg | 1 + admin/src/assets/icons/shezhi.svg | 1 + admin/src/assets/icons/shezhi_mian.svg | 1 + admin/src/assets/icons/shichang.svg | 1 + admin/src/assets/icons/shichang_mian.svg | 1 + admin/src/assets/icons/shijian.svg | 1 + admin/src/assets/icons/shijian_mian.svg | 1 + admin/src/assets/icons/shoudan.svg | 1 + admin/src/assets/icons/shouqi.svg | 1 + admin/src/assets/icons/shouqi_mian.svg | 1 + admin/src/assets/icons/shouye.svg | 1 + admin/src/assets/icons/shouye_mian.svg | 1 + admin/src/assets/icons/shouyiren.svg | 1 + admin/src/assets/icons/show.svg | 1 + .../assets/icons/shuangjiantouxiangyou.svg | 1 + .../assets/icons/shuangjiantouxiangzuo.svg | 1 + admin/src/assets/icons/shuaxin.svg | 1 + admin/src/assets/icons/shuju.svg | 1 + admin/src/assets/icons/shuju2.svg | 1 + admin/src/assets/icons/shuju_liuliang.svg | 1 + admin/src/assets/icons/shuju_mian.svg | 1 + admin/src/assets/icons/sort.svg | 1 + admin/src/assets/icons/sousuo.svg | 1 + admin/src/assets/icons/sucai.svg | 1 + admin/src/assets/icons/tianjia.svg | 1 + admin/src/assets/icons/tishi.svg | 1 + admin/src/assets/icons/tishi_mian.svg | 1 + admin/src/assets/icons/tongxunlu_mian.svg | 1 + admin/src/assets/icons/tongzhi.svg | 1 + admin/src/assets/icons/tongzhi_mian.svg | 1 + admin/src/assets/icons/tuichuquanping.svg | 1 + admin/src/assets/icons/tuiguang.svg | 1 + admin/src/assets/icons/tuiguang_mian.svg | 1 + admin/src/assets/icons/tupian.svg | 1 + admin/src/assets/icons/tupian_mian.svg | 1 + admin/src/assets/icons/user_biaoqian.svg | 1 + admin/src/assets/icons/user_gaikuang.svg | 1 + admin/src/assets/icons/user_guanli.svg | 1 + admin/src/assets/icons/wangpudiandan.svg | 1 + admin/src/assets/icons/weixin.svg | 1 + admin/src/assets/icons/weixin_mian.svg | 1 + admin/src/assets/icons/wode.svg | 1 + admin/src/assets/icons/wode_mian.svg | 1 + admin/src/assets/icons/xiangji.svg | 1 + admin/src/assets/icons/xiaoxi.svg | 1 + admin/src/assets/icons/xiazai.svg | 1 + admin/src/assets/icons/xitongquanxian.svg | 1 + admin/src/assets/icons/yingxiao_qipao.svg | 1 + admin/src/assets/icons/yingyezizhi.svg | 1 + admin/src/assets/icons/yinhangka.svg | 1 + admin/src/assets/icons/yiwen.svg | 1 + admin/src/assets/icons/youhui.svg | 1 + admin/src/assets/icons/youjian.svg | 1 + admin/src/assets/icons/youjiantou.svg | 1 + admin/src/assets/icons/yulibao.svg | 1 + admin/src/assets/icons/yuyin.svg | 1 + admin/src/assets/icons/yuyueguanli.svg | 1 + admin/src/assets/icons/yuyueguanlishezhi.svg | 1 + admin/src/assets/icons/zhankai.svg | 1 + admin/src/assets/icons/zhankai_mian.svg | 1 + admin/src/assets/icons/zhibo.svg | 1 + admin/src/assets/icons/zhibo_mian.svg | 1 + admin/src/assets/icons/zhuangxiu.svg | 1 + admin/src/assets/icons/zhuangxiu_mian.svg | 1 + admin/src/assets/icons/zhuoweiguanli.svg | 1 + admin/src/assets/icons/zichanzhuanrang.svg | 1 + admin/src/assets/icons/zuliao.svg | 1 + admin/src/assets/icons/zuliaoyuding.svg | 1 + admin/src/assets/images/icon_folder.png | Bin 0 -> 605 bytes admin/src/assets/images/no_perms.png | Bin 0 -> 14619 bytes admin/src/assets/images/theme_black.png | Bin 0 -> 2564 bytes admin/src/assets/images/theme_white.png | Bin 0 -> 2559 bytes admin/src/components/app-link/index.vue | 38 + admin/src/components/color-picker/index.vue | 33 + .../src/components/daterange-picker/index.vue | 44 + admin/src/components/del-wrap/index.vue | 51 ++ admin/src/components/dict-value/index.vue | 30 + admin/src/components/editor/index.vue | 143 +++ admin/src/components/footer-btns/index.vue | 30 + admin/src/components/icon/index.ts | 19 + admin/src/components/icon/index.vue | 49 ++ admin/src/components/icon/picker.vue | 185 ++++ admin/src/components/icon/svg-icon.vue | 38 + admin/src/components/image-contain/index.vue | 46 + admin/src/components/link/custom-link.vue | 42 + admin/src/components/link/index.ts | 11 + admin/src/components/link/index.vue | 96 ++ admin/src/components/link/picker.vue | 84 ++ admin/src/components/link/shop-pages.vue | 101 +++ admin/src/components/material/file.vue | 55 ++ admin/src/components/material/hook.ts | 208 +++++ admin/src/components/material/index.vue | 584 +++++++++++++ admin/src/components/material/picker.vue | 303 +++++++ admin/src/components/material/preview.vue | 72 ++ .../src/components/overflow-tooltip/index.vue | 47 + admin/src/components/pagination/index.vue | 50 ++ admin/src/components/popover-input/index.vue | 130 +++ admin/src/components/popup/index.vue | 133 +++ admin/src/components/upload/index.vue | 148 ++++ admin/src/components/video-player/index.vue | 72 ++ admin/src/config/index.ts | 10 + admin/src/config/setting.ts | 18 + admin/src/enums/appEnums.ts | 40 + admin/src/enums/cacheEnums.ts | 8 + admin/src/enums/pageEnum.ts | 9 + admin/src/enums/requestEnums.ts | 28 + admin/src/hooks/useDictOptions.ts | 69 ++ admin/src/hooks/useLockFn.ts | 21 + admin/src/hooks/useMultipleTabs.ts | 47 + admin/src/hooks/usePaging.ts | 62 ++ admin/src/hooks/useWatchRoute.ts | 17 + admin/src/install/directives/copy.ts | 28 + admin/src/install/directives/perms.ts | 28 + admin/src/install/index.ts | 27 + admin/src/install/plugins/element.ts | 11 + admin/src/install/plugins/pinia.ts | 6 + admin/src/install/plugins/router.ts | 6 + admin/src/layout/Empty.vue | 20 + admin/src/layout/components/footer.vue | 22 + .../default/components/header/breadcrumb.vue | 20 + .../layout/default/components/header/fold.vue | 15 + .../default/components/header/full-screen.vue | 10 + .../default/components/header/index.vue | 55 ++ .../components/header/multiple-tabs.vue | 122 +++ .../default/components/header/refresh.vue | 14 + .../components/header/user-drop-down.vue | 34 + admin/src/layout/default/components/main.vue | 26 + .../default/components/setting/drawer.vue | 220 +++++ .../default/components/setting/index.vue | 19 + .../default/components/sidebar/index.vue | 44 + .../default/components/sidebar/logo.vue | 61 ++ .../default/components/sidebar/menu-item.vue | 87 ++ .../default/components/sidebar/menu.vue | 101 +++ .../default/components/sidebar/side.vue | 66 ++ admin/src/layout/default/index.vue | 22 + admin/src/main.ts | 10 + admin/src/permission.ts | 84 ++ admin/src/router/index.ts | 110 +++ admin/src/router/routes.ts | 59 ++ admin/src/stores/index.ts | 3 + admin/src/stores/modules/app.ts | 51 ++ admin/src/stores/modules/multipleTabs.ts | 169 ++++ admin/src/stores/modules/setting.ts | 55 ++ admin/src/stores/modules/user.ts | 96 ++ admin/src/styles/dark.css | 49 ++ admin/src/styles/element.scss | 145 +++ admin/src/styles/index.scss | 6 + admin/src/styles/public.scss | 18 + admin/src/styles/tailwind.css | 3 + admin/src/styles/var.css | 48 + admin/src/utils/auth.ts | 18 + admin/src/utils/cache.ts | 50 ++ admin/src/utils/echart.ts | 65 ++ admin/src/utils/env.ts | 13 + admin/src/utils/feedback.ts | 95 ++ admin/src/utils/file.ts | 16 + admin/src/utils/request/axios.ts | 165 ++++ admin/src/utils/request/cancel.ts | 31 + admin/src/utils/request/index.ts | 130 +++ admin/src/utils/request/type.d.ts | 38 + admin/src/utils/theme.ts | 74 ++ admin/src/utils/util.ts | 171 ++++ admin/src/utils/validate.ts | 7 + admin/src/views/account/images/login_bg.png | Bin 0 -> 59273 bytes admin/src/views/account/login.vue | 130 +++ admin/src/views/article/column/edit.vue | 95 ++ admin/src/views/article/column/index.vue | 114 +++ admin/src/views/article/lists/edit.vue | 175 ++++ admin/src/views/article/lists/index.vue | 168 ++++ admin/src/views/article_collect/edit.vue | 116 +++ admin/src/views/article_collect/index.vue | 108 +++ admin/src/views/channel/h5.vue | 61 ++ admin/src/views/channel/weapp.vue | 177 ++++ admin/src/views/channel/wx_dev.vue | 62 ++ admin/src/views/channel/wx_oa/config.vue | 195 +++++ admin/src/views/channel/wx_oa/menu.vue | 45 + .../views/channel/wx_oa/menu_com/oa-attr.vue | 85 ++ .../wx_oa/menu_com/oa-menu-form-edit.vue | 73 ++ .../channel/wx_oa/menu_com/oa-menu-form.vue | 107 +++ .../views/channel/wx_oa/menu_com/oa-phone.vue | 121 +++ .../views/channel/wx_oa/menu_com/useMenuOa.ts | 164 ++++ .../channel/wx_oa/reply/default_reply.vue | 128 +++ admin/src/views/channel/wx_oa/reply/edit.vue | 189 ++++ .../channel/wx_oa/reply/follow_reply.vue | 129 +++ .../channel/wx_oa/reply/keyword_reply.vue | 145 +++ admin/src/views/consumer/lists/detail.vue | 134 +++ admin/src/views/consumer/lists/index.vue | 94 ++ .../views/decoration/component/add-nav.vue | 79 ++ .../decoration/component/decoration-img.vue | 59 ++ .../component/pages/attr-setting.vue | 28 + .../views/decoration/component/pages/menu.vue | 44 + .../decoration/component/pages/preview.vue | 67 ++ .../component/widgets/banner/attr.vue | 79 ++ .../component/widgets/banner/content.vue | 33 + .../component/widgets/banner/index.ts | 8 + .../component/widgets/banner/options.ts | 15 + .../widgets/customer-service/attr.vue | 38 + .../widgets/customer-service/content.vue | 39 + .../widgets/customer-service/index.ts | 8 + .../widgets/customer-service/options.ts | 11 + .../decoration/component/widgets/index.ts | 14 + .../component/widgets/my-service/attr.vue | 38 + .../component/widgets/my-service/content.vue | 59 ++ .../component/widgets/my-service/index.ts | 8 + .../component/widgets/my-service/options.ts | 16 + .../decoration/component/widgets/nav/attr.vue | 36 + .../component/widgets/nav/content.vue | 32 + .../decoration/component/widgets/nav/index.ts | 8 + .../component/widgets/nav/options.ts | 15 + .../component/widgets/news/attr.vue | 20 + .../component/widgets/news/content.vue | 70 ++ .../component/widgets/news/index.ts | 8 + .../component/widgets/news/options.ts | 7 + .../component/widgets/search/attr.vue | 20 + .../component/widgets/search/content.vue | 23 + .../component/widgets/search/index.ts | 8 + .../component/widgets/search/options.ts | 7 + .../component/widgets/user-banner/attr.vue | 79 ++ .../component/widgets/user-banner/content.vue | 32 + .../component/widgets/user-banner/index.ts | 8 + .../component/widgets/user-banner/options.ts | 15 + .../component/widgets/user-info/attr.vue | 20 + .../component/widgets/user-info/content.vue | 16 + .../user-info/images/default_avatar.png | Bin 0 -> 6093 bytes .../widgets/user-info/images/my_topbg.png | Bin 0 -> 142469 bytes .../component/widgets/user-info/index.ts | 8 + .../component/widgets/user-info/options.ts | 7 + admin/src/views/decoration/pages/index.vue | 105 +++ admin/src/views/decoration/tabbar.vue | 221 +++++ admin/src/views/dev_tools/code/edit.vue | 388 ++++++++ admin/src/views/dev_tools/code/index.vue | 246 ++++++ .../dev_tools/components/code-preview.vue | 73 ++ .../views/dev_tools/components/data-table.vue | 102 +++ admin/src/views/error/403.vue | 15 + admin/src/views/error/404.vue | 9 + admin/src/views/error/components/error.vue | 57 ++ admin/src/views/material/index.vue | 62 ++ admin/src/views/message/notice/edit.vue | 122 +++ admin/src/views/message/notice/index.vue | 95 ++ admin/src/views/message/short_letter/edit.vue | 130 +++ .../src/views/message/short_letter/index.vue | 56 ++ .../views/organization/department/edit.vue | 182 ++++ .../views/organization/department/index.vue | 166 ++++ admin/src/views/organization/post/edit.vue | 127 +++ admin/src/views/organization/post/index.vue | 128 +++ admin/src/views/permission/admin/edit.vue | 289 ++++++ admin/src/views/permission/admin/index.vue | 181 ++++ admin/src/views/permission/menu/edit.vue | 315 +++++++ admin/src/views/permission/menu/index.vue | 157 ++++ admin/src/views/permission/role/auth.vue | 154 ++++ admin/src/views/permission/role/edit.vue | 114 +++ admin/src/views/permission/role/index.vue | 108 +++ admin/src/views/setting/dict/data/edit.vue | 129 +++ admin/src/views/setting/dict/data/index.vue | 144 +++ admin/src/views/setting/dict/type/edit.vue | 111 +++ admin/src/views/setting/dict/type/index.vue | 135 +++ admin/src/views/setting/search/index.vue | 180 ++++ admin/src/views/setting/storage/edit.vue | 196 +++++ admin/src/views/setting/storage/index.vue | 53 ++ admin/src/views/setting/system/cache.vue | 261 ++++++ .../src/views/setting/system/environment.vue | 148 ++++ admin/src/views/setting/system/journal.vue | 128 +++ .../src/views/setting/user/login_register.vue | 180 ++++ admin/src/views/setting/user/setup.vue | 65 ++ admin/src/views/setting/website/filing.vue | 93 ++ .../src/views/setting/website/information.vue | 150 ++++ admin/src/views/setting/website/protocol.vue | 58 ++ admin/src/views/user/setting.vue | 162 ++++ .../workbench/image/customer_service.png | Bin 0 -> 85609 bytes .../src/views/workbench/image/menu_admin.png | Bin 0 -> 3197 bytes admin/src/views/workbench/image/menu_auth.png | Bin 0 -> 2969 bytes admin/src/views/workbench/image/menu_dept.png | Bin 0 -> 3047 bytes admin/src/views/workbench/image/menu_dict.png | Bin 0 -> 2162 bytes admin/src/views/workbench/image/menu_file.png | Bin 0 -> 2672 bytes .../views/workbench/image/menu_generator.png | Bin 0 -> 3264 bytes admin/src/views/workbench/image/menu_role.png | Bin 0 -> 4173 bytes admin/src/views/workbench/image/menu_web.png | Bin 0 -> 2156 bytes admin/src/views/workbench/image/qq_group.png | Bin 0 -> 33733 bytes admin/src/views/workbench/index.vue | 264 ++++++ admin/tailwind.config.js | 119 +++ admin/tsconfig.json | 19 + admin/typings/index.d.ts | 3 + admin/typings/router.d.ts | 14 + admin/vite.config.ts | 59 ++ docs/1.go-打包.md | 13 + docs/2.go-运行.md | 34 + docs/3.go-nginx配置.md | 27 + docs/assets/genCode.png | Bin 0 -> 300676 bytes docs/assets/work.png | Bin 0 -> 339639 bytes server.code-workspace | 14 + server/.env.example | 15 + server/.gitignore | 60 ++ server/.goreleaser.yaml | 46 + server/.vscode/launch.json | 15 + server/.vscode/settings.json | 10 + .../article_collect/article_collect_ctl.go | 64 ++ .../article_collect/article_collect_schema.go | 41 + .../article_collect_service.go | 122 +++ server/admin/article_collect_route.go | 26 + server/admin/common/album/route.go | 116 +++ server/admin/common/album/schema.go | 103 +++ server/admin/common/album/service.go | 225 +++++ server/admin/common/index/index.go | 41 + server/admin/common/index/service.go | 101 +++ server/admin/common/upload/service.go | 53 ++ server/admin/common/upload/upload.go | 60 ++ server/admin/entry.go | 47 + server/admin/monitor/monitor.go | 48 + server/admin/setting/copyright/copyright.go | 38 + server/admin/setting/copyright/schema.go | 7 + server/admin/setting/copyright/service.go | 44 + server/admin/setting/dict_data/dict_data.go | 96 ++ server/admin/setting/dict_data/schema.go | 55 ++ server/admin/setting/dict_data/service.go | 153 ++++ server/admin/setting/dict_type/dict_type.go | 92 ++ server/admin/setting/dict_type/schema.go | 48 + server/admin/setting/dict_type/service.go | 133 +++ server/admin/setting/protocol/protocol.go | 45 + server/admin/setting/protocol/schema.go | 13 + server/admin/setting/protocol/service.go | 67 ++ server/admin/setting/storage/schema.go | 23 + server/admin/setting/storage/service.go | 103 +++ server/admin/setting/storage/storage.go | 66 ++ server/admin/setting/website/schema.go | 11 + server/admin/setting/website/service.go | 66 ++ server/admin/setting/website/website.go | 45 + server/admin/system/admin/admin.go | 114 +++ server/admin/system/admin/schema.go | 102 +++ server/admin/system/admin/service.go | 407 +++++++++ server/admin/system/dept/dept.go | 86 ++ server/admin/system/dept/schema.go | 53 ++ server/admin/system/dept/service.go | 151 ++++ server/admin/system/enter.go | 51 ++ server/admin/system/log/log.go | 59 ++ server/admin/system/log/schema.go | 54 ++ server/admin/system/log/service.go | 122 +++ server/admin/system/login/login.go | 49 ++ server/admin/system/login/schema.go | 39 + server/admin/system/login/service.go | 148 ++++ server/admin/system/menu/menu.go | 88 ++ server/admin/system/menu/schema.go | 69 ++ server/admin/system/menu/service.go | 141 +++ server/admin/system/post/post.go | 92 ++ server/admin/system/post/schema.go | 51 ++ server/admin/system/post/service.go | 157 ++++ server/admin/system/role/authPermService.go | 116 +++ server/admin/system/role/role.go | 86 ++ server/admin/system/role/schema.go | 55 ++ server/admin/system/role/service.go | 186 ++++ server/config/admin.go | 96 ++ server/config/config.go | 122 +++ server/config/gen.go | 17 + server/core/db.go | 76 ++ server/core/logger.go | 21 + server/core/redis.go | 29 + server/core/request/common.go | 7 + server/core/response/common.go | 9 + server/core/response/error.go | 15 + server/core/response/response.go | 208 +++++ server/core/time.go | 53 ++ server/core/uaparser.go | 5 + server/generator/enter.go | 11 + server/generator/gen/gen.go | 161 ++++ server/generator/gen/schema.go | 179 ++++ server/generator/gen/service.go | 448 ++++++++++ .../templates/gocode/controller.go copy.tpl | 82 ++ .../templates/gocode/controller.go.tpl | 64 ++ .../generator/templates/gocode/model.go.tpl | 10 + .../generator/templates/gocode/route.go.tpl | 25 + .../generator/templates/gocode/schema.go.tpl | 57 ++ .../generator/templates/gocode/service.go.tpl | 141 +++ server/generator/templates/vue/api.ts.tpl | 26 + server/generator/templates/vue/edit.vue.tpl | 252 ++++++ .../templates/vue/index-tree.vue.tpl | 222 +++++ server/generator/templates/vue/index.vue.tpl | 183 ++++ server/generator/tpl_utils/constants.go | 93 ++ server/generator/tpl_utils/tpl.go | 279 ++++++ server/generator/tpl_utils/utils.go | 214 +++++ server/go.mod | 74 ++ server/go.sum | 647 ++++++++++++++ server/main.go | 69 ++ server/middleware/auth.go | 149 ++++ server/middleware/cors.go | 17 + server/middleware/error.go | 37 + server/middleware/log.go | 129 +++ server/middleware/show.go | 24 + server/model/article_collect.go | 12 + server/model/common/album.go | 30 + server/model/gen/gen.go | 48 + server/model/setting/dict_data.go | 16 + server/model/setting/dict_type.go | 14 + server/model/system/system.go | 130 +++ server/plugin/storage.go | 134 +++ server/routers/enter.go | 13 + server/static/backend_avatar.png | Bin 0 -> 158102 bytes server/static/backend_backdrop.png | Bin 0 -> 85612 bytes server/static/backend_favicon.ico | Bin 0 -> 5253 bytes server/static/backend_logo.png | Bin 0 -> 8604 bytes server/util/array.go | 38 + server/util/config.go | 75 ++ server/util/convert.go | 27 + server/util/ip.go | 23 + server/util/redis.go | 214 +++++ server/util/server.go | 171 ++++ server/util/string.go | 34 + server/util/tools.go | 97 ++ server/util/url.go | 63 ++ server/util/verify.go | 71 ++ sql/x_admin.sql | 826 ++++++++++++++++++ 611 files changed, 28854 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 admin/.editorconfig create mode 100644 admin/.env.development.example create mode 100644 admin/.env.production.example create mode 100644 admin/.eslintrc.cjs create mode 100644 admin/.gitignore create mode 100644 admin/.prettierrc create mode 100644 admin/.vscode/extensions.json create mode 100644 admin/.vscode/settings.json create mode 100644 admin/README.md create mode 100644 admin/global.d.ts create mode 100644 admin/index.html create mode 100644 admin/package.json create mode 100644 admin/postcss.config.js create mode 100644 admin/scripts/build.mjs create mode 100644 admin/src/App.vue create mode 100644 admin/src/api/app.ts create mode 100644 admin/src/api/article.ts create mode 100644 admin/src/api/article_collect.ts create mode 100644 admin/src/api/channel/h5.ts create mode 100644 admin/src/api/channel/weapp.ts create mode 100644 admin/src/api/channel/wx_dev.ts create mode 100644 admin/src/api/channel/wx_oa.ts create mode 100644 admin/src/api/consumer.ts create mode 100644 admin/src/api/decoration.ts create mode 100644 admin/src/api/file.ts create mode 100644 admin/src/api/message.ts create mode 100644 admin/src/api/org/department.ts create mode 100644 admin/src/api/org/post.ts create mode 100644 admin/src/api/perms/admin.ts create mode 100644 admin/src/api/perms/menu.ts create mode 100644 admin/src/api/perms/role.ts create mode 100644 admin/src/api/setting/dict.ts create mode 100644 admin/src/api/setting/search.ts create mode 100644 admin/src/api/setting/storage.ts create mode 100644 admin/src/api/setting/system.ts create mode 100644 admin/src/api/setting/user.ts create mode 100644 admin/src/api/setting/website.ts create mode 100644 admin/src/api/tools/code.ts create mode 100644 admin/src/api/user.ts create mode 100644 admin/src/assets/icons/Androidfanhui.svg create mode 100644 admin/src/assets/icons/KMSguanli.svg create mode 100644 admin/src/assets/icons/KTVyuding.svg create mode 100644 admin/src/assets/icons/a-tixingdengpao.svg create mode 100644 admin/src/assets/icons/anquan.svg create mode 100644 admin/src/assets/icons/anquan_mian.svg create mode 100644 admin/src/assets/icons/anquan_mian1.svg create mode 100644 admin/src/assets/icons/banxing_mian.svg create mode 100644 admin/src/assets/icons/baoxian.svg create mode 100644 admin/src/assets/icons/bendishenghuodaxue.svg create mode 100644 admin/src/assets/icons/bianji.svg create mode 100644 admin/src/assets/icons/biaoqing.svg create mode 100644 admin/src/assets/icons/bukejian.svg create mode 100644 admin/src/assets/icons/caipinguanli.svg create mode 100644 admin/src/assets/icons/caiwu.svg create mode 100644 admin/src/assets/icons/caiwu_jifen.svg create mode 100644 admin/src/assets/icons/caiwu_tixian.svg create mode 100644 admin/src/assets/icons/canyinfuwu.svg create mode 100644 admin/src/assets/icons/carryout.svg create mode 100644 admin/src/assets/icons/chexiao.svg create mode 100644 admin/src/assets/icons/chihuohongbao.svg create mode 100644 admin/src/assets/icons/chuangyiwuliao.svg create mode 100644 admin/src/assets/icons/close.svg create mode 100644 admin/src/assets/icons/daiyunying.svg create mode 100644 admin/src/assets/icons/danwei.svg create mode 100644 admin/src/assets/icons/danxuankuang.svg create mode 100644 admin/src/assets/icons/danxuanxuanzhong.svg create mode 100644 admin/src/assets/icons/dayin.svg create mode 100644 admin/src/assets/icons/dayin_mian.svg create mode 100644 admin/src/assets/icons/del.svg create mode 100644 admin/src/assets/icons/diancanshezhi.svg create mode 100644 admin/src/assets/icons/dianhua.svg create mode 100644 admin/src/assets/icons/dianhua_mian.svg create mode 100644 admin/src/assets/icons/dianpu_fengge.svg create mode 100644 admin/src/assets/icons/dianputuijian.svg create mode 100644 admin/src/assets/icons/dianzifapiao.svg create mode 100644 admin/src/assets/icons/dingcan.svg create mode 100644 admin/src/assets/icons/dingdan.svg create mode 100644 admin/src/assets/icons/dingdan1.svg create mode 100644 admin/src/assets/icons/dingdan_mian.svg create mode 100644 admin/src/assets/icons/dingwei.svg create mode 100644 admin/src/assets/icons/dingwei_mian.svg create mode 100644 admin/src/assets/icons/ditu.svg create mode 100644 admin/src/assets/icons/ditu_mian.svg create mode 100644 admin/src/assets/icons/duizhang.svg create mode 100644 admin/src/assets/icons/elemo.svg create mode 100644 admin/src/assets/icons/ezhanggui.svg create mode 100644 admin/src/assets/icons/falvfuwubaoxiaohei.svg create mode 100644 admin/src/assets/icons/fengniaopaotui.svg create mode 100644 admin/src/assets/icons/fenxiang.svg create mode 100644 admin/src/assets/icons/fukuan.svg create mode 100644 admin/src/assets/icons/fukuan_mian.svg create mode 100644 admin/src/assets/icons/fullscreen-exit.svg create mode 100644 admin/src/assets/icons/fullscreen.svg create mode 100644 admin/src/assets/icons/fuwushichang.svg create mode 100644 admin/src/assets/icons/fuzhi.svg create mode 100644 admin/src/assets/icons/gaode.svg create mode 100644 admin/src/assets/icons/gengduo.svg create mode 100644 admin/src/assets/icons/gengduoandroid.svg create mode 100644 admin/src/assets/icons/gift.svg create mode 100644 admin/src/assets/icons/gongyingshang.svg create mode 100644 admin/src/assets/icons/goods.svg create mode 100644 admin/src/assets/icons/gou.svg create mode 100644 admin/src/assets/icons/gouwuche.svg create mode 100644 admin/src/assets/icons/gouxuan.svg create mode 100644 admin/src/assets/icons/gouxuan_mian.svg create mode 100644 admin/src/assets/icons/guanbi.svg create mode 100644 admin/src/assets/icons/guanli.svg create mode 100644 admin/src/assets/icons/guanli_mian.svg create mode 100644 admin/src/assets/icons/gukefapiao.svg create mode 100644 admin/src/assets/icons/haibaosheji.svg create mode 100644 admin/src/assets/icons/heshoujilu.svg create mode 100644 admin/src/assets/icons/heshoujilu1.svg create mode 100644 admin/src/assets/icons/hexiao_order.svg create mode 100644 admin/src/assets/icons/hide-2.svg create mode 100644 admin/src/assets/icons/hide.svg create mode 100644 admin/src/assets/icons/hongbao.svg create mode 100644 admin/src/assets/icons/huiche.svg create mode 100644 admin/src/assets/icons/huiyuanyingxiao.svg create mode 100644 admin/src/assets/icons/huodongbaoming.svg create mode 100644 admin/src/assets/icons/huodongguanli.svg create mode 100644 admin/src/assets/icons/huodongzhongxin.svg create mode 100644 admin/src/assets/icons/huojian.svg create mode 100644 admin/src/assets/icons/huojian_mian.svg create mode 100644 admin/src/assets/icons/huolala.svg create mode 100644 admin/src/assets/icons/iOSfanhui.svg create mode 100644 admin/src/assets/icons/jia.svg create mode 100644 admin/src/assets/icons/jia_mian.svg create mode 100644 admin/src/assets/icons/jian.svg create mode 100644 admin/src/assets/icons/jian_mian.svg create mode 100644 admin/src/assets/icons/jianpan.svg create mode 100644 admin/src/assets/icons/jianpanshanchu.svg create mode 100644 admin/src/assets/icons/jianshao.svg create mode 100644 admin/src/assets/icons/jiaopeiwangputong.svg create mode 100644 admin/src/assets/icons/jiaoyi.svg create mode 100644 admin/src/assets/icons/jiedan.svg create mode 100644 admin/src/assets/icons/jiekuan.svg create mode 100644 admin/src/assets/icons/jingshi.svg create mode 100644 admin/src/assets/icons/jingshi_mian.svg create mode 100644 admin/src/assets/icons/jingshi_mian1.svg create mode 100644 admin/src/assets/icons/jingyin.svg create mode 100644 admin/src/assets/icons/jingyin_mian.svg create mode 100644 admin/src/assets/icons/jingying.svg create mode 100644 admin/src/assets/icons/jingying_mian.svg create mode 100644 admin/src/assets/icons/jingyinggonglve.svg create mode 100644 admin/src/assets/icons/jingzhunyingxiao.svg create mode 100644 admin/src/assets/icons/jinhuo.svg create mode 100644 admin/src/assets/icons/kaitongwaimai.svg create mode 100644 admin/src/assets/icons/kanjia.svg create mode 100644 admin/src/assets/icons/kefu.svg create mode 100644 admin/src/assets/icons/kejian.svg create mode 100644 admin/src/assets/icons/kejian_mian.svg create mode 100644 admin/src/assets/icons/keziyuyue.svg create mode 100644 admin/src/assets/icons/kezizhongxin.svg create mode 100644 admin/src/assets/icons/koubei.svg create mode 100644 admin/src/assets/icons/kuaijiehuifu.svg create mode 100644 admin/src/assets/icons/ladu_mian.svg create mode 100644 admin/src/assets/icons/lanyadingwei.svg create mode 100644 admin/src/assets/icons/list-2.svg create mode 100644 admin/src/assets/icons/mendiandongtai.svg create mode 100644 admin/src/assets/icons/mishiyuding.svg create mode 100644 admin/src/assets/icons/mishiyuding1.svg create mode 100644 admin/src/assets/icons/notice_buyer.svg create mode 100644 admin/src/assets/icons/open.svg create mode 100644 admin/src/assets/icons/paiduiquhao.svg create mode 100644 admin/src/assets/icons/paimai.svg create mode 100644 admin/src/assets/icons/pingjia.svg create mode 100644 admin/src/assets/icons/pingtaifapiao.svg create mode 100644 admin/src/assets/icons/pinpai.svg create mode 100644 admin/src/assets/icons/qianbao.svg create mode 100644 admin/src/assets/icons/qianbao_mian.svg create mode 100644 admin/src/assets/icons/qiehuan.svg create mode 100644 admin/src/assets/icons/qingchu.svg create mode 100644 admin/src/assets/icons/qingchu_mian.svg create mode 100644 admin/src/assets/icons/qishoupeisong.svg create mode 100644 admin/src/assets/icons/qiyedingcan.svg create mode 100644 admin/src/assets/icons/qiyedingcan1.svg create mode 100644 admin/src/assets/icons/quanbu.svg create mode 100644 admin/src/assets/icons/quanping.svg create mode 100644 admin/src/assets/icons/qudao.svg create mode 100644 admin/src/assets/icons/qudao_xiaochengxu.svg create mode 100644 admin/src/assets/icons/rencaizhaopin.svg create mode 100644 admin/src/assets/icons/rili.svg create mode 100644 admin/src/assets/icons/rili2.svg create mode 100644 admin/src/assets/icons/rizhi.svg create mode 100644 admin/src/assets/icons/saoma.svg create mode 100644 admin/src/assets/icons/set_pay.svg create mode 100644 admin/src/assets/icons/set_peisong.svg create mode 100644 admin/src/assets/icons/set_user.svg create mode 100644 admin/src/assets/icons/set_weihu.svg create mode 100644 admin/src/assets/icons/shanchu.svg create mode 100644 admin/src/assets/icons/shanchu_mian.svg create mode 100644 admin/src/assets/icons/shangchuan.svg create mode 100644 admin/src/assets/icons/shangchuanzhaopian.svg create mode 100644 admin/src/assets/icons/shangpinguanli.svg create mode 100644 admin/src/assets/icons/shangpinzhushou.svg create mode 100644 admin/src/assets/icons/shangpuyuding.svg create mode 100644 admin/src/assets/icons/shebeiguanli.svg create mode 100644 admin/src/assets/icons/shengfuwangputong.svg create mode 100644 admin/src/assets/icons/shengyin.svg create mode 100644 admin/src/assets/icons/shengyin_mian.svg create mode 100644 admin/src/assets/icons/shezhi.svg create mode 100644 admin/src/assets/icons/shezhi_mian.svg create mode 100644 admin/src/assets/icons/shichang.svg create mode 100644 admin/src/assets/icons/shichang_mian.svg create mode 100644 admin/src/assets/icons/shijian.svg create mode 100644 admin/src/assets/icons/shijian_mian.svg create mode 100644 admin/src/assets/icons/shoudan.svg create mode 100644 admin/src/assets/icons/shouqi.svg create mode 100644 admin/src/assets/icons/shouqi_mian.svg create mode 100644 admin/src/assets/icons/shouye.svg create mode 100644 admin/src/assets/icons/shouye_mian.svg create mode 100644 admin/src/assets/icons/shouyiren.svg create mode 100644 admin/src/assets/icons/show.svg create mode 100644 admin/src/assets/icons/shuangjiantouxiangyou.svg create mode 100644 admin/src/assets/icons/shuangjiantouxiangzuo.svg create mode 100644 admin/src/assets/icons/shuaxin.svg create mode 100644 admin/src/assets/icons/shuju.svg create mode 100644 admin/src/assets/icons/shuju2.svg create mode 100644 admin/src/assets/icons/shuju_liuliang.svg create mode 100644 admin/src/assets/icons/shuju_mian.svg create mode 100644 admin/src/assets/icons/sort.svg create mode 100644 admin/src/assets/icons/sousuo.svg create mode 100644 admin/src/assets/icons/sucai.svg create mode 100644 admin/src/assets/icons/tianjia.svg create mode 100644 admin/src/assets/icons/tishi.svg create mode 100644 admin/src/assets/icons/tishi_mian.svg create mode 100644 admin/src/assets/icons/tongxunlu_mian.svg create mode 100644 admin/src/assets/icons/tongzhi.svg create mode 100644 admin/src/assets/icons/tongzhi_mian.svg create mode 100644 admin/src/assets/icons/tuichuquanping.svg create mode 100644 admin/src/assets/icons/tuiguang.svg create mode 100644 admin/src/assets/icons/tuiguang_mian.svg create mode 100644 admin/src/assets/icons/tupian.svg create mode 100644 admin/src/assets/icons/tupian_mian.svg create mode 100644 admin/src/assets/icons/user_biaoqian.svg create mode 100644 admin/src/assets/icons/user_gaikuang.svg create mode 100644 admin/src/assets/icons/user_guanli.svg create mode 100644 admin/src/assets/icons/wangpudiandan.svg create mode 100644 admin/src/assets/icons/weixin.svg create mode 100644 admin/src/assets/icons/weixin_mian.svg create mode 100644 admin/src/assets/icons/wode.svg create mode 100644 admin/src/assets/icons/wode_mian.svg create mode 100644 admin/src/assets/icons/xiangji.svg create mode 100644 admin/src/assets/icons/xiaoxi.svg create mode 100644 admin/src/assets/icons/xiazai.svg create mode 100644 admin/src/assets/icons/xitongquanxian.svg create mode 100644 admin/src/assets/icons/yingxiao_qipao.svg create mode 100644 admin/src/assets/icons/yingyezizhi.svg create mode 100644 admin/src/assets/icons/yinhangka.svg create mode 100644 admin/src/assets/icons/yiwen.svg create mode 100644 admin/src/assets/icons/youhui.svg create mode 100644 admin/src/assets/icons/youjian.svg create mode 100644 admin/src/assets/icons/youjiantou.svg create mode 100644 admin/src/assets/icons/yulibao.svg create mode 100644 admin/src/assets/icons/yuyin.svg create mode 100644 admin/src/assets/icons/yuyueguanli.svg create mode 100644 admin/src/assets/icons/yuyueguanlishezhi.svg create mode 100644 admin/src/assets/icons/zhankai.svg create mode 100644 admin/src/assets/icons/zhankai_mian.svg create mode 100644 admin/src/assets/icons/zhibo.svg create mode 100644 admin/src/assets/icons/zhibo_mian.svg create mode 100644 admin/src/assets/icons/zhuangxiu.svg create mode 100644 admin/src/assets/icons/zhuangxiu_mian.svg create mode 100644 admin/src/assets/icons/zhuoweiguanli.svg create mode 100644 admin/src/assets/icons/zichanzhuanrang.svg create mode 100644 admin/src/assets/icons/zuliao.svg create mode 100644 admin/src/assets/icons/zuliaoyuding.svg create mode 100644 admin/src/assets/images/icon_folder.png create mode 100644 admin/src/assets/images/no_perms.png create mode 100644 admin/src/assets/images/theme_black.png create mode 100644 admin/src/assets/images/theme_white.png create mode 100644 admin/src/components/app-link/index.vue create mode 100644 admin/src/components/color-picker/index.vue create mode 100644 admin/src/components/daterange-picker/index.vue create mode 100644 admin/src/components/del-wrap/index.vue create mode 100644 admin/src/components/dict-value/index.vue create mode 100644 admin/src/components/editor/index.vue create mode 100644 admin/src/components/footer-btns/index.vue create mode 100644 admin/src/components/icon/index.ts create mode 100644 admin/src/components/icon/index.vue create mode 100644 admin/src/components/icon/picker.vue create mode 100644 admin/src/components/icon/svg-icon.vue create mode 100644 admin/src/components/image-contain/index.vue create mode 100644 admin/src/components/link/custom-link.vue create mode 100644 admin/src/components/link/index.ts create mode 100644 admin/src/components/link/index.vue create mode 100644 admin/src/components/link/picker.vue create mode 100644 admin/src/components/link/shop-pages.vue create mode 100644 admin/src/components/material/file.vue create mode 100644 admin/src/components/material/hook.ts create mode 100644 admin/src/components/material/index.vue create mode 100644 admin/src/components/material/picker.vue create mode 100644 admin/src/components/material/preview.vue create mode 100644 admin/src/components/overflow-tooltip/index.vue create mode 100644 admin/src/components/pagination/index.vue create mode 100644 admin/src/components/popover-input/index.vue create mode 100644 admin/src/components/popup/index.vue create mode 100644 admin/src/components/upload/index.vue create mode 100644 admin/src/components/video-player/index.vue create mode 100644 admin/src/config/index.ts create mode 100644 admin/src/config/setting.ts create mode 100644 admin/src/enums/appEnums.ts create mode 100644 admin/src/enums/cacheEnums.ts create mode 100644 admin/src/enums/pageEnum.ts create mode 100644 admin/src/enums/requestEnums.ts create mode 100644 admin/src/hooks/useDictOptions.ts create mode 100644 admin/src/hooks/useLockFn.ts create mode 100644 admin/src/hooks/useMultipleTabs.ts create mode 100644 admin/src/hooks/usePaging.ts create mode 100644 admin/src/hooks/useWatchRoute.ts create mode 100644 admin/src/install/directives/copy.ts create mode 100644 admin/src/install/directives/perms.ts create mode 100644 admin/src/install/index.ts create mode 100644 admin/src/install/plugins/element.ts create mode 100644 admin/src/install/plugins/pinia.ts create mode 100644 admin/src/install/plugins/router.ts create mode 100644 admin/src/layout/Empty.vue create mode 100644 admin/src/layout/components/footer.vue create mode 100644 admin/src/layout/default/components/header/breadcrumb.vue create mode 100644 admin/src/layout/default/components/header/fold.vue create mode 100644 admin/src/layout/default/components/header/full-screen.vue create mode 100644 admin/src/layout/default/components/header/index.vue create mode 100644 admin/src/layout/default/components/header/multiple-tabs.vue create mode 100644 admin/src/layout/default/components/header/refresh.vue create mode 100644 admin/src/layout/default/components/header/user-drop-down.vue create mode 100644 admin/src/layout/default/components/main.vue create mode 100644 admin/src/layout/default/components/setting/drawer.vue create mode 100644 admin/src/layout/default/components/setting/index.vue create mode 100644 admin/src/layout/default/components/sidebar/index.vue create mode 100644 admin/src/layout/default/components/sidebar/logo.vue create mode 100644 admin/src/layout/default/components/sidebar/menu-item.vue create mode 100644 admin/src/layout/default/components/sidebar/menu.vue create mode 100644 admin/src/layout/default/components/sidebar/side.vue create mode 100644 admin/src/layout/default/index.vue create mode 100644 admin/src/main.ts create mode 100644 admin/src/permission.ts create mode 100644 admin/src/router/index.ts create mode 100644 admin/src/router/routes.ts create mode 100644 admin/src/stores/index.ts create mode 100644 admin/src/stores/modules/app.ts create mode 100644 admin/src/stores/modules/multipleTabs.ts create mode 100644 admin/src/stores/modules/setting.ts create mode 100644 admin/src/stores/modules/user.ts create mode 100644 admin/src/styles/dark.css create mode 100644 admin/src/styles/element.scss create mode 100644 admin/src/styles/index.scss create mode 100644 admin/src/styles/public.scss create mode 100644 admin/src/styles/tailwind.css create mode 100644 admin/src/styles/var.css create mode 100644 admin/src/utils/auth.ts create mode 100644 admin/src/utils/cache.ts create mode 100644 admin/src/utils/echart.ts create mode 100644 admin/src/utils/env.ts create mode 100644 admin/src/utils/feedback.ts create mode 100644 admin/src/utils/file.ts create mode 100644 admin/src/utils/request/axios.ts create mode 100644 admin/src/utils/request/cancel.ts create mode 100644 admin/src/utils/request/index.ts create mode 100644 admin/src/utils/request/type.d.ts create mode 100644 admin/src/utils/theme.ts create mode 100644 admin/src/utils/util.ts create mode 100644 admin/src/utils/validate.ts create mode 100644 admin/src/views/account/images/login_bg.png create mode 100644 admin/src/views/account/login.vue create mode 100644 admin/src/views/article/column/edit.vue create mode 100644 admin/src/views/article/column/index.vue create mode 100644 admin/src/views/article/lists/edit.vue create mode 100644 admin/src/views/article/lists/index.vue create mode 100644 admin/src/views/article_collect/edit.vue create mode 100644 admin/src/views/article_collect/index.vue create mode 100644 admin/src/views/channel/h5.vue create mode 100644 admin/src/views/channel/weapp.vue create mode 100644 admin/src/views/channel/wx_dev.vue create mode 100644 admin/src/views/channel/wx_oa/config.vue create mode 100644 admin/src/views/channel/wx_oa/menu.vue create mode 100644 admin/src/views/channel/wx_oa/menu_com/oa-attr.vue create mode 100644 admin/src/views/channel/wx_oa/menu_com/oa-menu-form-edit.vue create mode 100644 admin/src/views/channel/wx_oa/menu_com/oa-menu-form.vue create mode 100644 admin/src/views/channel/wx_oa/menu_com/oa-phone.vue create mode 100644 admin/src/views/channel/wx_oa/menu_com/useMenuOa.ts create mode 100644 admin/src/views/channel/wx_oa/reply/default_reply.vue create mode 100644 admin/src/views/channel/wx_oa/reply/edit.vue create mode 100644 admin/src/views/channel/wx_oa/reply/follow_reply.vue create mode 100644 admin/src/views/channel/wx_oa/reply/keyword_reply.vue create mode 100644 admin/src/views/consumer/lists/detail.vue create mode 100644 admin/src/views/consumer/lists/index.vue create mode 100644 admin/src/views/decoration/component/add-nav.vue create mode 100644 admin/src/views/decoration/component/decoration-img.vue create mode 100644 admin/src/views/decoration/component/pages/attr-setting.vue create mode 100644 admin/src/views/decoration/component/pages/menu.vue create mode 100644 admin/src/views/decoration/component/pages/preview.vue create mode 100644 admin/src/views/decoration/component/widgets/banner/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/banner/content.vue create mode 100644 admin/src/views/decoration/component/widgets/banner/index.ts create mode 100644 admin/src/views/decoration/component/widgets/banner/options.ts create mode 100644 admin/src/views/decoration/component/widgets/customer-service/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/customer-service/content.vue create mode 100644 admin/src/views/decoration/component/widgets/customer-service/index.ts create mode 100644 admin/src/views/decoration/component/widgets/customer-service/options.ts create mode 100644 admin/src/views/decoration/component/widgets/index.ts create mode 100644 admin/src/views/decoration/component/widgets/my-service/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/my-service/content.vue create mode 100644 admin/src/views/decoration/component/widgets/my-service/index.ts create mode 100644 admin/src/views/decoration/component/widgets/my-service/options.ts create mode 100644 admin/src/views/decoration/component/widgets/nav/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/nav/content.vue create mode 100644 admin/src/views/decoration/component/widgets/nav/index.ts create mode 100644 admin/src/views/decoration/component/widgets/nav/options.ts create mode 100644 admin/src/views/decoration/component/widgets/news/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/news/content.vue create mode 100644 admin/src/views/decoration/component/widgets/news/index.ts create mode 100644 admin/src/views/decoration/component/widgets/news/options.ts create mode 100644 admin/src/views/decoration/component/widgets/search/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/search/content.vue create mode 100644 admin/src/views/decoration/component/widgets/search/index.ts create mode 100644 admin/src/views/decoration/component/widgets/search/options.ts create mode 100644 admin/src/views/decoration/component/widgets/user-banner/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/user-banner/content.vue create mode 100644 admin/src/views/decoration/component/widgets/user-banner/index.ts create mode 100644 admin/src/views/decoration/component/widgets/user-banner/options.ts create mode 100644 admin/src/views/decoration/component/widgets/user-info/attr.vue create mode 100644 admin/src/views/decoration/component/widgets/user-info/content.vue create mode 100644 admin/src/views/decoration/component/widgets/user-info/images/default_avatar.png create mode 100644 admin/src/views/decoration/component/widgets/user-info/images/my_topbg.png create mode 100644 admin/src/views/decoration/component/widgets/user-info/index.ts create mode 100644 admin/src/views/decoration/component/widgets/user-info/options.ts create mode 100644 admin/src/views/decoration/pages/index.vue create mode 100644 admin/src/views/decoration/tabbar.vue create mode 100644 admin/src/views/dev_tools/code/edit.vue create mode 100644 admin/src/views/dev_tools/code/index.vue create mode 100644 admin/src/views/dev_tools/components/code-preview.vue create mode 100644 admin/src/views/dev_tools/components/data-table.vue create mode 100644 admin/src/views/error/403.vue create mode 100644 admin/src/views/error/404.vue create mode 100644 admin/src/views/error/components/error.vue create mode 100644 admin/src/views/material/index.vue create mode 100644 admin/src/views/message/notice/edit.vue create mode 100644 admin/src/views/message/notice/index.vue create mode 100644 admin/src/views/message/short_letter/edit.vue create mode 100644 admin/src/views/message/short_letter/index.vue create mode 100644 admin/src/views/organization/department/edit.vue create mode 100644 admin/src/views/organization/department/index.vue create mode 100644 admin/src/views/organization/post/edit.vue create mode 100644 admin/src/views/organization/post/index.vue create mode 100644 admin/src/views/permission/admin/edit.vue create mode 100644 admin/src/views/permission/admin/index.vue create mode 100644 admin/src/views/permission/menu/edit.vue create mode 100644 admin/src/views/permission/menu/index.vue create mode 100644 admin/src/views/permission/role/auth.vue create mode 100644 admin/src/views/permission/role/edit.vue create mode 100644 admin/src/views/permission/role/index.vue create mode 100644 admin/src/views/setting/dict/data/edit.vue create mode 100644 admin/src/views/setting/dict/data/index.vue create mode 100644 admin/src/views/setting/dict/type/edit.vue create mode 100644 admin/src/views/setting/dict/type/index.vue create mode 100644 admin/src/views/setting/search/index.vue create mode 100644 admin/src/views/setting/storage/edit.vue create mode 100644 admin/src/views/setting/storage/index.vue create mode 100644 admin/src/views/setting/system/cache.vue create mode 100644 admin/src/views/setting/system/environment.vue create mode 100644 admin/src/views/setting/system/journal.vue create mode 100644 admin/src/views/setting/user/login_register.vue create mode 100644 admin/src/views/setting/user/setup.vue create mode 100644 admin/src/views/setting/website/filing.vue create mode 100644 admin/src/views/setting/website/information.vue create mode 100644 admin/src/views/setting/website/protocol.vue create mode 100644 admin/src/views/user/setting.vue create mode 100644 admin/src/views/workbench/image/customer_service.png create mode 100644 admin/src/views/workbench/image/menu_admin.png create mode 100644 admin/src/views/workbench/image/menu_auth.png create mode 100644 admin/src/views/workbench/image/menu_dept.png create mode 100644 admin/src/views/workbench/image/menu_dict.png create mode 100644 admin/src/views/workbench/image/menu_file.png create mode 100644 admin/src/views/workbench/image/menu_generator.png create mode 100644 admin/src/views/workbench/image/menu_role.png create mode 100644 admin/src/views/workbench/image/menu_web.png create mode 100644 admin/src/views/workbench/image/qq_group.png create mode 100644 admin/src/views/workbench/index.vue create mode 100644 admin/tailwind.config.js create mode 100644 admin/tsconfig.json create mode 100644 admin/typings/index.d.ts create mode 100644 admin/typings/router.d.ts create mode 100644 admin/vite.config.ts create mode 100644 docs/1.go-打包.md create mode 100644 docs/2.go-运行.md create mode 100644 docs/3.go-nginx配置.md create mode 100644 docs/assets/genCode.png create mode 100644 docs/assets/work.png create mode 100644 server.code-workspace create mode 100644 server/.env.example create mode 100644 server/.gitignore create mode 100644 server/.goreleaser.yaml create mode 100644 server/.vscode/launch.json create mode 100644 server/.vscode/settings.json create mode 100644 server/admin/article_collect/article_collect_ctl.go create mode 100644 server/admin/article_collect/article_collect_schema.go create mode 100644 server/admin/article_collect/article_collect_service.go create mode 100644 server/admin/article_collect_route.go create mode 100644 server/admin/common/album/route.go create mode 100644 server/admin/common/album/schema.go create mode 100644 server/admin/common/album/service.go create mode 100644 server/admin/common/index/index.go create mode 100644 server/admin/common/index/service.go create mode 100644 server/admin/common/upload/service.go create mode 100644 server/admin/common/upload/upload.go create mode 100644 server/admin/entry.go create mode 100644 server/admin/monitor/monitor.go create mode 100644 server/admin/setting/copyright/copyright.go create mode 100644 server/admin/setting/copyright/schema.go create mode 100644 server/admin/setting/copyright/service.go create mode 100644 server/admin/setting/dict_data/dict_data.go create mode 100644 server/admin/setting/dict_data/schema.go create mode 100644 server/admin/setting/dict_data/service.go create mode 100644 server/admin/setting/dict_type/dict_type.go create mode 100644 server/admin/setting/dict_type/schema.go create mode 100644 server/admin/setting/dict_type/service.go create mode 100644 server/admin/setting/protocol/protocol.go create mode 100644 server/admin/setting/protocol/schema.go create mode 100644 server/admin/setting/protocol/service.go create mode 100644 server/admin/setting/storage/schema.go create mode 100644 server/admin/setting/storage/service.go create mode 100644 server/admin/setting/storage/storage.go create mode 100644 server/admin/setting/website/schema.go create mode 100644 server/admin/setting/website/service.go create mode 100644 server/admin/setting/website/website.go create mode 100644 server/admin/system/admin/admin.go create mode 100644 server/admin/system/admin/schema.go create mode 100644 server/admin/system/admin/service.go create mode 100644 server/admin/system/dept/dept.go create mode 100644 server/admin/system/dept/schema.go create mode 100644 server/admin/system/dept/service.go create mode 100644 server/admin/system/enter.go create mode 100644 server/admin/system/log/log.go create mode 100644 server/admin/system/log/schema.go create mode 100644 server/admin/system/log/service.go create mode 100644 server/admin/system/login/login.go create mode 100644 server/admin/system/login/schema.go create mode 100644 server/admin/system/login/service.go create mode 100644 server/admin/system/menu/menu.go create mode 100644 server/admin/system/menu/schema.go create mode 100644 server/admin/system/menu/service.go create mode 100644 server/admin/system/post/post.go create mode 100644 server/admin/system/post/schema.go create mode 100644 server/admin/system/post/service.go create mode 100644 server/admin/system/role/authPermService.go create mode 100644 server/admin/system/role/role.go create mode 100644 server/admin/system/role/schema.go create mode 100644 server/admin/system/role/service.go create mode 100644 server/config/admin.go create mode 100644 server/config/config.go create mode 100644 server/config/gen.go create mode 100644 server/core/db.go create mode 100644 server/core/logger.go create mode 100644 server/core/redis.go create mode 100644 server/core/request/common.go create mode 100644 server/core/response/common.go create mode 100644 server/core/response/error.go create mode 100644 server/core/response/response.go create mode 100644 server/core/time.go create mode 100644 server/core/uaparser.go create mode 100644 server/generator/enter.go create mode 100644 server/generator/gen/gen.go create mode 100644 server/generator/gen/schema.go create mode 100644 server/generator/gen/service.go create mode 100644 server/generator/templates/gocode/controller.go copy.tpl create mode 100644 server/generator/templates/gocode/controller.go.tpl create mode 100644 server/generator/templates/gocode/model.go.tpl create mode 100644 server/generator/templates/gocode/route.go.tpl create mode 100644 server/generator/templates/gocode/schema.go.tpl create mode 100644 server/generator/templates/gocode/service.go.tpl create mode 100644 server/generator/templates/vue/api.ts.tpl create mode 100644 server/generator/templates/vue/edit.vue.tpl create mode 100644 server/generator/templates/vue/index-tree.vue.tpl create mode 100644 server/generator/templates/vue/index.vue.tpl create mode 100644 server/generator/tpl_utils/constants.go create mode 100644 server/generator/tpl_utils/tpl.go create mode 100644 server/generator/tpl_utils/utils.go create mode 100644 server/go.mod create mode 100644 server/go.sum create mode 100644 server/main.go create mode 100644 server/middleware/auth.go create mode 100644 server/middleware/cors.go create mode 100644 server/middleware/error.go create mode 100644 server/middleware/log.go create mode 100644 server/middleware/show.go create mode 100644 server/model/article_collect.go create mode 100644 server/model/common/album.go create mode 100644 server/model/gen/gen.go create mode 100644 server/model/setting/dict_data.go create mode 100644 server/model/setting/dict_type.go create mode 100644 server/model/system/system.go create mode 100644 server/plugin/storage.go create mode 100644 server/routers/enter.go create mode 100644 server/static/backend_avatar.png create mode 100644 server/static/backend_backdrop.png create mode 100644 server/static/backend_favicon.ico create mode 100644 server/static/backend_logo.png create mode 100644 server/util/array.go create mode 100644 server/util/config.go create mode 100644 server/util/convert.go create mode 100644 server/util/ip.go create mode 100644 server/util/redis.go create mode 100644 server/util/server.go create mode 100644 server/util/string.go create mode 100644 server/util/tools.go create mode 100644 server/util/url.go create mode 100644 server/util/verify.go create mode 100644 sql/x_admin.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ae4f47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Build and Release Folders +.idea +bin-debug/ +bin-release/ +[Oo]bj/ +[Bb]in/ + +# Other files and folders +.settings/ + +# Executables +*.swf +*.air +*.ipa +*.apk + +/public/uploads/* +!/public/uploads/index.html +frontend/* +# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` +# should NOT be excluded as they contain compiler settings and other important +# information for Eclipse / Flash Builder. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4742ef0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 likeadmin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5f93f9 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +
^XtXVb(VsXt>u(VL?5E4^{oeMzzJ8Jqmq*@mktblN@+cYu;z4WCv(P;+lm;-7SKpgng<(qx+@V4 zkjFZQYL-_yB?Sm`BZJ;y%_p(y8V@J24%;4ly-G)Hso8ws*|_e`sa58;J8m$1K2JVV zQ~Ds;Ms;J64xe@gC*wxcc;s; I}2c zO **e`jIuBG4Y2{_%3l5L5zxn<<&>M~Q$fLvXPwSJ!RmL?73o*OO*N zzl0uWNbcKR=O&&1{`mV1JcZ*s$7Uy4X2BPRsJX~Ik|8yte(3#jIY!`f=>lqJbQ)sa zRj~7XIcXOO+`t%bUD5Mr;${-3zn8hiDhhxtn@$&I`IvRQYwU$f)ukb?EuG(hX}6gE zQBh9YoQ+*4kok7B*qjD+rA_ys!?-@7XUVvk{EU?n>RsN2bxuu=@H+Z~1HA+Zu%| zyi)nH)<8kn_Iv7TSeeB5k64tEwk6h|pSl9SJ&|@dlG*h?cR*wn^rRY*t};m2MCj26 z)a;xBzwB>B28b>onul0CI14HVB2>fD hT3{hb#v{Pwtf5y~K X->2bNti>2}hWzPbF#H5~lLbES0y{r(hzPt7h*tv$=j zPWfSTPqCGSsVSyg 87SSxcan!51ST6=wL z$1!q>^*Z2{gYH}@ImLvsxH1Hxx)S{{81Z>YZb%;E%7Lry^7?n(@Fgxc+DShv2@bw0 z3rE<^;m`*pP%R40u@P1O;O1bxjU2+}`OY@Y#!&Lc?X$^{sJ^Uo!DcEN*X=E# GA zf#xI}xl68MwGD_{Q$xHt24S=Q$2i6CQH#~cSPko^fuG^_ol(c!Ch70KJU 4`IJKmGXr=d$r?6KqfnC22%_Mr=!2M$q~ z;`Wsa_8(_a{F&?7R2F&td+(dKcs4dDJ_<)j6O%mq#k;$^+3*avckOInIW*|i8R==J zJRX^+_c~`4G$`<;`@w!T3J#87=pPq=bl0NTQ>a|^AUxE^V~OWd&>Z-@d)^Yu8tPlV z0EGC*7q}4shT-95>VPc{qqAiapNG8n1$ALOZXvzPyu+O9_3kX+o_^AqIwK9!hfI;V zOLHOcVbB$Ny;|TNX;hYe^%? _LxjZ5y7opM;|RQz z`htw(T?0C2V*HqmHdmgPm8fHgFjSrSI3V#j*QXIj|J~LN7Y#l9wMgu8Wltf0)A=$k zT_c9Dji#}wv8VS RFOyN}rH&GC~ve_vTGz?`#jf(jKBYdx B&BAW%;29a*V}`49Q!_4&TDpQ;QBZ(u?+Z;+Rw9vBBjbO)u5zSJV8YA zqJ0RMkiXppxWrIk6@|bsj@G>xX>+c4r@cc1+COHFDLM^)2n|U>h%wAZ-Q}|JkPAh| zIN06Ppuj62Wjd622VSn;)SWA@vnN1VCSP2Mpl7)OH*mM>cn0+f`+OxTB 4 z9`(I 8AvccDP$Z1b=vKfd&X0Q*>A9` qPAp4Ea4&N)c`cl4CZH|p zahKELiy-oLS`^=l&UlZ#BZ9xxkT~5HO+oD@QYJPZLn(LaVS^9%5Y&HJc389zqeQrF z+HuRs!T!zlD?GN0i!U5UE-8Ec=@9&uvKHtidK~q>NfvtpOUr3esb#n$vHTQwhv<5x zQXOkF>Z0#+oIxE8GQR8Z*w#m7y0dvSPMVD_%oN1)2d;oXXQ$iC=3-bhM2)Bj4X78Y zs6v}t `>prEg jXR$SsGub#zn@ve4;+C|gYQ;AUtpzR& z;U~#GT*=?wodvcGCgNY3%+d_lc5d`{_0HD&k$=qW6(2{=QsT7c+ECjmm{{EujkEKQ z82;TyzdRp*nh1*s=8=-$n>txLf@nNy*f2)&88_5F-frwX=3KMp%excV|H+#^d}M{% z6-}WoNNna6lJe(OGiqT^2Z9TmC} >O{f)V_ag zFRJc*Xi06iM>NL_-TX-=St{>T>6FzOzIMR>O}D!7X(FlG$I{>$RfK{OS6qMafbUd; z_tqM&h8EiGxW?T2HIAlbJEz0Qk ^fzqqjMM{U);B{QKD&2 zKnlNxYBOJLCFu5J3tTtyQ4{?=aR4>pvT` >6_gL|7 z_nnDwOT+COxO}mO+#vR3ASyYr7k{8R)%gbfZ}PP6TEaob^AF%fht8tws2qD}^wUdl z+UYMc!77T5L24&C?*8F%xlg9R#@ox1XW63zXZ~l*$f>_=qumM-0!Flj`PSXl2WPG> zQX5?te8qv_diQg&`Nx|#dU_e6meT5-T9QBKFfZ %NHO(~10@tQ z W4Qt1W|9NL^TbAXeGSK^dKX65|<&I89&C{Sgs*v)}vZzGo+w6K7g18ONYR7h& zue(2>tA>44$3LK|ImT=$m`qD7uilXGCMi(mZTem1`Q3E#1+RUe{TPC~tnkQhaxm<; z{NyoZ`+;=RY|v#dC&J4oZ$`!nQ$!|G;(0}bwkmdl(bjnP)mI$qG^$T!_eA6M=VPuE zI`IA9QSDMSnQlb?ri;4_9nDQd%gcu%M_INqd&O?dud$O&Z-Z%`n&0YkIZ`qdGwXRs zU@(u#wXQJA5fh}%gxP=0=)KPDIua!+MtkZ)0d}*l2YjPSmid1B7wF-lqo^bHdF@!> z*rw-o;^nk4e%~&IEF_NR{$34%8)M$ii;23qBeMd>pUp;oy~mq@QKN=*>pAL4aY?jD zD}nwl`TT-d78wE}!Qsjy6oevm7Yri!PS#=E2Fqp}#4jr(GFUgC&CsB-ig|-9)dH;a z=MT kC;412yKtMEc-y>aySx3`^+A%dt`+!zP=ef`$LbH$SdxG zqf@e6k`oiD% rJ# ~`^cC$f>U%elrc}R9)fMPVe2GmmD9EpSw2?RulQsN?M%?(Ga{T>n02OMU z{GsgYw!Sby;PnP)`st%C?+@>u3ajij%=uKbn%jvXB7{ m+45WT!Mh&Ux!yc{0$y8VufEs6 zMB2l3EfPsec`=WfzJAL`$JlD_KBhLAgC};kwRBqH q-4W9{6W WQ 0b `YR1E^_ex;%zn z#do& xL5A{RkmeVJ7L~t@F_7k0X_7_G#jQ4j4vg EV*rDA#GllM~*6DLy2wv0TaFv%A z)_=I&Y%UfX&Z3m=lDt<_{o^kRTj_HrP$uVXKT@ZPJE2ugwuj#E5NCRIHPHmn@K%V8 z9IrB7(0+Qqg?umo>USli^)W|K?lj&QP^Le#BmY?(wxXj?SQ}tszIC>TwP+A6DBJ!C ziy+ER9@6`mtIaL&tc43-k^)24`QUy|MKzb0fky?D14@V?3S?j(DWbxW@t-1sUw2R$ z1qCK7{yZN!3Z1DgyE6BkmzY~dQ!dm-kFJ{(7EYGZyLio(uktg2j`u_m%hGt-pD@fD zECVXAjQT5qz5Wn7Nzvqx^`~$0xaAxhIiYBb6`Ki~783Z1=j{gHmYgoySC=hXc@oZT zc+NY5*hna4hG5AGk5fq^Mg|Zj-7i=ivg`IcsmWxVx#C2xbtK;B5gUBja!0xdge}L0 z3C72|)7A#uj1IS~Fb4H?Z)Pz!3fG`2w_`K+4qN5fQOYdr>3OSUd4)B(<)GXIzk}b? zI|wIqC*jTY*1#=iH$=6`PF?>hT+zh*fx9FrWz%DbFt)Ir{-8iHB~x1M;?|DcXaiw% z_n5xXrD$ ~k6(zCcdL zLCgz@KyWQ(@x`dVlGeu|=xd5Q8pWtE&|E%Zwj^ON{ha{! g# Xg^CW^P- zRaT=vc?T8SP+r$Jw~-Ql*;=Tj@6>y{slfwsj^$~MJIHJ``eUNvN}K=Z52~~Cx4VVC z9~m3lBX4@g(QFH$_9Af+AD^s*C|~*ArlQep(jFho@CH}5a{OjzB>uh1aeA+@>kTkb zK$jmePJ_^Wa3o%gIM+nHzdoQ%88C(IVdwtT=dtfSl&e@A8~`7-#05*wU~jY`SjkT8 zyc~184Ck^t476$9jL6$8xmWG^htnVFPN#OhldvukojTTZ7n1Tl|6RIU>HEC=<2qNc z;H}^frkSj`*Z3%)J0XuT5*cxXMtXy;0*8l9#~G*geo&PaqUOF_1+cwDXyXLlc7WmR z+UMCgSlf?uzIJ$Ef7s-R9bf5^2IGmCYV$Ad+@&`4jp;=tt$KcJ=bG3NiBT3aB+@_( z>kTY2Tkz^?E%{H|(TH#K76C8pJ^3Y-A^4baP@&0Ug=aQ6(yPr4A!~U33NETr>`onv zzQuAyuEq1|Ym}jvZP)p SMz zcfrGN$)Xn6yI Pw5g)=yV;Mm5)RQ6%7?B{<+>=H`ZYk)GZc z5*3hh4~qD&N(Iq1dy>d2*SnU)%l23drcl_TD%Lw_3HAC-FkcJIT+2!Pc?r&&DyJfE zWZcD@hm)z3^F7;j`+=rTa!XROUZ1GVZ>@k1QLGS5kdN=TT&0z!IKHwSPtSC6ntl~5 zx@xQ9d9a694R2=WbL~t%^ug*=`Nk{)h=-a3-kO4Jb|@LV?p05EUPY_E8-v>N_d4;C z)Rpw$56<)lbsT6KHrP_gW)%6E+wVopiHCNsI4wS6a6a&wZr!)dHvdVIwld_4V&5BF zXc|sR=pM`38|!WOj`Y_^YmrLEbjMe hy83r)NwnI`>MZTfOYp(eFTPu5(JZb-JxZ_N&V*(K?0p{hEFin zUO*vv)ra+C{^q+|zMmio6etX(?0r#ZNxX7x$O{fE0=cF)V#^gyBDtb9h-|hAK!)TU z4=xI#93=nK6Y=pcHfItq=QH(Oh9qJ|iJt}eCw5dVhqH;7Yugc{p>J|-m_U(HqU(s7 za>mvte`%F|bc%1z;>ke_OLoL5bCuB~&)Whr%;sDz$S!{hYEJRiw|x`S3by1&6cfkT zrtWb2NS;Ko`lkDXtCX>rfwAYp$RjS~$*#Iq@^Cvn*PY6#$D2{$&u1!3A`meyPHW^6 z!=G=dqH~}S{Bpbx5%lWkN}{<1k1~rx-Z+fbqvd=PpX%G_{IXX|P$K+9cTu`l0$Wj? zbXY2nH`kCaj!cd0CcnlqqQpvWF}V^xz%CySD)I$sBRBigMW>Wmm~d1+yn}L3VG4kp zKQVn lxlnh-ygeubDj6J`EE zh&tYyKvos?Kph#>`D^FBoz+~-dQb*Gi4RAj(k2-)f*$n>mzulFvug?*_$uYjF1 T56lg71;?c9r7-cb0d;CPd}g2Nm7TW~)@hJv9ZDI0;ZL>& z{1<4t+7Z|o9-rfut5nonT=$Y7Tvm!RfR{)vpPne(T3{w8KC829{RF3sytnl%rMJnH zV?pvr2XV3O`{gaSz4hl4Rm5LX6mf{W9J;l-o;c^`TFNM *5Mt)RLY4HmDbJ^wIKNtA?#Pg<9%YvgR$+H+K=pNB1u zSTi MYLo~PgsM>KmLLsMzK!7CTID+jy%$U;*}*6`-$ z*41t7`2GHpcDf)x^X_7&u;ku}R1b>)33^Y@q2VowVq@H(g}K!z(s-fH-}KfmCIz7i z;j^IMtDz4blYjfdx&IoM2x?<3-=8A>V^mQ cP02wb8w9>*Rbqor1yJe{#ZeBS;&>^#PZXS8(&>NqE)oG6Wq->%CDbxIjf3&t zAk2Axzg82kqQ+Dm$TM7dZbBeH_0+E7850LI 6HE)0XVvdx`sSP01=b! zjkd|hY{Xgcebk~&A}pt%nrOHbQPTu=<%$Dvd~L)Wcvb=KTV&w{!zdV)MQ-t2dv?-_ zSl;jghESEvnybh8)}~EgW^d6*z%zoWoUZl0Y!I0Ya0!}tVz%E8^4d *#3XO)!m (d(;@$avaejjV%9HYp{_lLxc`BYwsAcM6zf~pD-e=|&TXOrb!f`c_ zbyc8N{+_1Sq^7J5X@edjGK$TBwFS7Bj=^3DgWMkAY7RF+AO7y`bFw~@y)}NilK?o( z=_op`4#4=3GN=@C{Nv;Lvm(1v5-a?171pP%63{xG*aOJiUAP82(;A>sXQXJ}kFvS* z4M#^mcfMe>w&&B@v4c5|);%n7Qx$|(W^ilI? pbpcRur5Wg_m|3K|7eG12oVXu zxeDvW2(2C(tr6Shq)rCjgd5U254$P0C_(#5TzxPnm$D0Yb~y z>84JgtU4M$5_*SmEI0!fOFEq$li`nVDZh8RTNX>B0 _ z4a$sBT;cv5)OrngoE;Or?j>ECIb$+KRWu@i1#mT$aEqruBNAZ&Po-vB# 5S0Wlgql!e2;c)Glu#AoLo_025cG*O zAyNV)L?DP1A&DYIN`OQm*dR&?c(mi)4{vU6?oYU#+1;7lnf>P5GPBw4ZfE6X_sIeP z0D0$gr!VdJ<{iBu4cxIF%DomlZdc4HXAkKeNt8z21OQ|h&Ziwb;(h1IDWUl=^&VmC zgsfHYfo0E#?Q>7HJXk~2*Ne9;wvV{ryo|8yCRlf2sUB+gu5{%h+AU}FsJ?Px{eJ6f zBY84LrC|eE5UHTeaV& I`Q@w_qVtBDF#kD zr2;S@n~^QZq`If> zF>@ULSVKs=-WVc-TY-L{N>$kl>vrp$98W+wfB5BE-oiLGIhzdGDygHyCRMaypLz}k zO=)@VS0&1eL?Sx`93G3bfJC}_%=g_gWbr`aHqkt0h@W 5Ym{ &=Z}!1YEzt-LMEt8rg0X-@5tvr&N)1*H#5# zBAK1GEK?)gy7-MzLgJ~4y;WZb^DpPM0cq(OXKRCoQ^~87a*NfxU_yE1LwX93+X2)+ zH&D(?_9a5fLZO%r6F+PTun82O3S5OTwGW+0(|!&BOL*@p8Y6ewc1~3lBNCxlXW2_v zWq5l)$5L~)MDr8KaUm2gGa?{_$bqL#6vRSs5rV0w9Z6|O$YPhSn_*I&T(0DuGz7*~ zovVczzJMatsXstuATUrsb5Q@yRG5l8lYK>@)%gAwJ&RNZZbF1a_FO2s!Yg=2pl{YI za$es_!~b424PW8*5h1aBx2D69t*xJCz6>IX|H<2vPpO$685kRDWgU|g=O%m0gnZ27 zsm7gu-A0Dm%^RVe1VAq5=m95~Cjj@!KB_kv3;^?h+)&qEi<2PpLd~7DGP{)?j0}xo z8>7^~(@L7!+E+`!kyGC)lNL6{O)4o@G^;38tT H}jsxdcstem`LBwLi8{6W7T_Y3wJf zv*cqWu!ZEK5-6o~dI0vXx@ZC>&vEuDKEJD zehqtnNx)TwKnU8F_KV}}5ET{0&nfq785>+*xApz78;5Td=dkSU?4}?oTN`SPN-hfF zQJQ(HlNz0Dgc5x<7c*ABS-CXn*`}ePu|l;K<8qj@vlTz%6KRc&E7{%MPIVsy{{4gy z$>#8Oo3NiNWRSg;xhh07nD4i+RHaUsc;07YDI6UgT{ST=`Q0XNVdHYB<1!Pza8JGQ zaP)LdeO$|#0NVt|PP{I%RfIsNY%?c0rPcOdBZl*zR6!#qi&d9qY2qXjseO-}-1otz z&D^pC43Ih+gcui}38 A_moYDd-+ma4BTx zM^)-1p&YjRLheuX#RC%G4a;V9@|P)DqWC}@?uQEkF_9?{2r5*ig-_9!`)h8#ls|q@ z%93|{h&!{`22L?;GWn>Mn!>^8coK Q5b0cYb^A>)sLx~TQT7^M zsSb8Wx8F~eIlsF&Q6)ozyOH$Eg2B%upXaH{AC;gam}(+2G#{_$n0uxxm>3sJ_uYoP zt7pS%&UFjmkG!f*s Oyf-)j!~@nh#IEIbea{ zY}hG9Z%?N5HI2ajrjhhv7wTex15nE!QF$Pjuab8G2n!yu>89qyve{BY={}OpQxRny ze877Z_xb40joK|X2b@&;l(E;(KaGmEmim&<`82c4WJ`a>j4QTU1DMy(mpfLj1F;Po zfOhZ9Md4;O-`ZH?vE}o #(0df8Xds4|T0FDfk7D_dV6KrZ-Z!pj2JY+)h(Y`KSF03bE>= literal 0 HcmV?d00001 diff --git a/admin/src/assets/images/theme_white.png b/admin/src/assets/images/theme_white.png new file mode 100644 index 0000000000000000000000000000000000000000..52714ba146ac6aa1510c5615f7c5dce913cab9cb GIT binary patch literal 2559 zcmeH}`8U)J7r OTM+_* zC_6f!&PqH@qDk^{66>My$xPy8h<1)1@)C)Wzmg1rDAYKjY&{|`&J1C$`FLt X*Tnk>ej*Z zf8|s+lTTvqZpYx%%rNFz?UCZSSptzb^8HDaxSh3W_lf(hme{#5{;i$VV+a5JOzLR) zN$pp{rgv`fC-`y+pcjNrWL0~6U!eCex7~Ulai~jltJGuiwID3IE_npdoYb<>{QT6) zgF8vV*}go3*e+w`4jK$T?SvkmzQtO!+FPM;RJ7>enR=hI9Jyq%5lx1pofK7WzLX-f z$Ji^zUa#omokBXUYrJ{vV(X*f@W^N6;qzo)QfJJOFPc$4DT$mr!Nqvnh6phak>eD2 z)z6QQsv0nQRT%{UfH3{f0E_hC(cNtbw}ToXCPFM0ixiJEY<6@%N5i8cH3>G~GUL#V zIGCwND_**y?>hI(_(F;I$?W_##GRscoGdDF@b r$^d z)(YgmaUYI8aM<9_!4`?EZLF`vPC!Ft!id2b0ly!eSuA7xJFV l8jTwBpCg z%7fkXjfwe;W3-RMcE%%I;nJm#X(Xd8_{ThaZu+_?ge=Z}D#|C9a3hq3BB)<#9Wk zi_#CZs0O74*0_z9c^U0UvIZ4vcy%OKm*O;i#RyjLNL}>G>gb20Sh?BuW$=X(a_5d6 z87Z+e<6&SfC2%`FxHVU|t>=|%(8|ovO1-mY%Y`Z`dwP0$Kr&w;mq{CF47?eAj1I>y z%3)Z+&Y*u+gZrVJi|u#@{hhPscH&Bf|7@WdLC97g _;bAWdxCw}mmXVr*i3~~SI&7L{P_K@ zr++Yj{=ZUi6ZmjE-z7Yh(x62ET=zG~LS}!>mM Kb8auJ*G2%u$nAKprVQ3~`=r oVa_*|I4IKN3z}5FXh|5O%V+gu zK{jSIIdH3l$u%*2D23<0Bz!cJlQ7Qt8jesT!QIM#m$ZcWf7}1*l1&KWWIY%U>mGfC zm{&yG)HI@=xs)Xy$96{|&Qbe<6B82;Fg%LAs7p8~GH+&or!EH6k6%ag4Myx`e!O81 z4i2t$cXty7L$|rP!v!To>b8q&P-g@JQIwXJ7V|j}*~MUpP!S8>K0Yy_{{Dh{_pmzL z%hVfs3*I{BIl3t)9=|7ac6Pd+c5&f}IA;ZD+1XVK;ftdDf&!Qg>F1A!pU!s-mL@v4 zPh$=n?!Xf#a_Ly}$miXw6>bMVJuu(g0zL}0<*Hq?EZE#*Md0fzg^-NSa8;U(;jeAp z^s*wTL}w5NgN-OFE7uKFdp65&1x-`?0#r0z4LUQ@(@9gKxqF91gKBGqZ_9Np^F&El z%9HFvIZkW;oVpyR7vHPi?j!`&Ne?sXt)K-qVUt}ffx?T73pR2d#0Y=z`X8o2VnI_= z6AYcPXRQ(D$*gLUKNAnwW!@HP`-^<*!lwQ$G&eW@Syt{>zy_LdfEXV>Pd!&rf84@i ziNx?BYg2zhBfN6NoNy!(8Ow~E7odYUPu9-C?0@-TU7ZqI)QGb+;o{|FouD7B58$z; z$B)O9dK;bKQuNm)%X*LCQG|WMC7Zn5+ OnyS@kd1Ok7f zLw<#>LhlZ4hD=@V74Ce|z@tR3>-8K|Y+cHU(tLP9g9f{!Nu8?KM4*ZbXamxrpk8Bn zgr7QnhFBFKq(dn=l^$8$tg_#zu3z5qmMjKN!x3cQT#ayDOC05|Z*EU9?uSeH9PVGa z#X6sI(6?8_oOtce8T)wz)zxZELpbD@NLhl4<+m%2(Uhx3I$Ozhf59$?fi;Km333EA zb%wl}9JVz?*i|1iSTl{90?Fn~_<{+@_^NMIJf SL#SxZT2-Oc?JRh;{h#OM(~=8N zYh^=EIT>M=yE+4f0A*Fq&OwPl2#F2%(q5?_G3EXJ5bEoFcW$Gw970$T^=r@ntq6(u cufQ*r+E9`TEXaOJ^3g#YPr0Dp+W95?2N6QvSpWb4 literal 0 HcmV?d00001 diff --git a/admin/src/components/app-link/index.vue b/admin/src/components/app-link/index.vue new file mode 100644 index 0000000..e7fe926 --- /dev/null +++ b/admin/src/components/app-link/index.vue @@ -0,0 +1,38 @@ + + + + + + diff --git a/admin/src/components/color-picker/index.vue b/admin/src/components/color-picker/index.vue new file mode 100644 index 0000000..ed0bc3e --- /dev/null +++ b/admin/src/components/color-picker/index.vue @@ -0,0 +1,33 @@ + ++ ++ + diff --git a/admin/src/components/daterange-picker/index.vue b/admin/src/components/daterange-picker/index.vue new file mode 100644 index 0000000..9f97531 --- /dev/null +++ b/admin/src/components/daterange-picker/index.vue @@ -0,0 +1,44 @@ + ++ + 重置 ++ + + diff --git a/admin/src/components/del-wrap/index.vue b/admin/src/components/del-wrap/index.vue new file mode 100644 index 0000000..787f3e2 --- /dev/null +++ b/admin/src/components/del-wrap/index.vue @@ -0,0 +1,51 @@ + + ++ + + + + diff --git a/admin/src/components/dict-value/index.vue b/admin/src/components/dict-value/index.vue new file mode 100644 index 0000000..2935e5d --- /dev/null +++ b/admin/src/components/dict-value/index.vue @@ -0,0 +1,30 @@ + ++ +++ + + {{ index != 0 ? '、' : '' }}{{ item.name }} + ++ + diff --git a/admin/src/components/editor/index.vue b/admin/src/components/editor/index.vue new file mode 100644 index 0000000..ae39eee --- /dev/null +++ b/admin/src/components/editor/index.vue @@ -0,0 +1,143 @@ + +++ + + + diff --git a/admin/src/components/footer-btns/index.vue b/admin/src/components/footer-btns/index.vue new file mode 100644 index 0000000..eb5aea2 --- /dev/null +++ b/admin/src/components/footer-btns/index.vue @@ -0,0 +1,30 @@ + + + + + + + diff --git a/admin/src/components/icon/index.ts b/admin/src/components/icon/index.ts new file mode 100644 index 0000000..831fcad --- /dev/null +++ b/admin/src/components/icon/index.ts @@ -0,0 +1,19 @@ +import * as ElementPlusIcons from '@element-plus/icons-vue' +//@ts-ignore +import localIconsName from 'virtual:svg-icons-names' + +export const LOCAL_ICON_PREFIX = 'local-icon-' +export const EL_ICON_PREFIX = 'el-icon-' + +const elIconsName: string[] = [] + +for (const [, component] of Object.entries(ElementPlusIcons)) { + elIconsName.push(`${EL_ICON_PREFIX}${component.name}`) +} + +export function getElementPlusIconNames() { + return elIconsName +} +export function getLocalIconNames() { + return localIconsName +} diff --git a/admin/src/components/icon/index.vue b/admin/src/components/icon/index.vue new file mode 100644 index 0000000..738eef3 --- /dev/null +++ b/admin/src/components/icon/index.vue @@ -0,0 +1,49 @@ + diff --git a/admin/src/components/icon/picker.vue b/admin/src/components/icon/picker.vue new file mode 100644 index 0000000..27fc8cd --- /dev/null +++ b/admin/src/components/icon/picker.vue @@ -0,0 +1,185 @@ + ++ + + ++ + + diff --git a/admin/src/components/icon/svg-icon.vue b/admin/src/components/icon/svg-icon.vue new file mode 100644 index 0000000..10c6919 --- /dev/null +++ b/admin/src/components/icon/svg-icon.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/admin/src/components/image-contain/index.vue b/admin/src/components/image-contain/index.vue new file mode 100644 index 0000000..1961025 --- /dev/null +++ b/admin/src/components/image-contain/index.vue @@ -0,0 +1,46 @@ + ++ +++ +++++ +请选择图标++ + {{ item.name }} + +++++ ++++++ ++ + + + +++ + 无 + + ++ ++ + + ++ + + + + + diff --git a/admin/src/components/link/custom-link.vue b/admin/src/components/link/custom-link.vue new file mode 100644 index 0000000..d90264e --- /dev/null +++ b/admin/src/components/link/custom-link.vue @@ -0,0 +1,42 @@ + + ++ + + diff --git a/admin/src/components/link/index.ts b/admin/src/components/link/index.ts new file mode 100644 index 0000000..58f3673 --- /dev/null +++ b/admin/src/components/link/index.ts @@ -0,0 +1,11 @@ +export enum LinkTypeEnum { + 'SHOP_PAGES' = 'shop', + 'CUSTOM_LINK' = 'custom' +} + +export interface Link { + path: string + name?: string + type: string + query?: Record+ 自定义链接 +++++ + 请填写完整的带有“https://”或“http://”的链接地址,链接的域名必须在微信公众平台设置业务域名 +++} diff --git a/admin/src/components/link/index.vue b/admin/src/components/link/index.vue new file mode 100644 index 0000000..c3acb1b --- /dev/null +++ b/admin/src/components/link/index.vue @@ -0,0 +1,96 @@ + + ++ + + + + diff --git a/admin/src/components/link/picker.vue b/admin/src/components/link/picker.vue new file mode 100644 index 0000000..cc89913 --- /dev/null +++ b/admin/src/components/link/picker.vue @@ -0,0 +1,84 @@ + ++ ++ {{ item.name }} + ++++ + ++ + + + + diff --git a/admin/src/components/link/shop-pages.vue b/admin/src/components/link/shop-pages.vue new file mode 100644 index 0000000..e5c747b --- /dev/null +++ b/admin/src/components/link/shop-pages.vue @@ -0,0 +1,101 @@ + ++ + ++ + + + ++ ++ + + diff --git a/admin/src/components/material/file.vue b/admin/src/components/material/file.vue new file mode 100644 index 0000000..a9f0500 --- /dev/null +++ b/admin/src/components/material/file.vue @@ -0,0 +1,55 @@ + ++++ {{ item.name }} ++++ + + + + diff --git a/admin/src/components/material/hook.ts b/admin/src/components/material/hook.ts new file mode 100644 index 0000000..e490c9a --- /dev/null +++ b/admin/src/components/material/hook.ts @@ -0,0 +1,208 @@ +import { + fileCateAdd, + fileCateDelete, + fileCateEdit, + fileCateLists, + fileDelete, + fileList, + fileMove, + fileRename +} from '@/api/file' +import { usePaging } from '@/hooks/usePaging' +import feedback from '@/utils/feedback' +import { ElMessage, ElTree, type CheckboxValueType } from 'element-plus' +import { shallowRef, type Ref } from 'vue' + +// 左侧分组的钩子函数 +export function useCate(type: number) { + const treeRef = shallowRef+++ + +++ + >() + // 分组列表 + const cateLists = ref ([]) + + // 选中的分组id + const cateId = ref ('') + + // 获取分组列表 + const getCateLists = async () => { + const data = await fileCateLists({ + type + }) + const item: any[] = [ + // { + // name: '全部', + // id: '' + // }, + // { + // name: '未分组', + // id: 0 + // } + ] + cateLists.value = data + cateLists.value.unshift(...item) + setTimeout(() => { + treeRef.value?.setCurrentKey(cateId.value) + }, 0) + } + + // 添加分组 + const handleAddCate = async (value: string) => { + await fileCateAdd({ + type, + name: value, + pid: 0 + }) + getCateLists() + } + + // 编辑分组 + const handleEditCate = async (value: string, id: number) => { + await fileCateEdit({ + id, + name: value + }) + getCateLists() + } + + // 删除分组 + const handleDeleteCate = async (id: number) => { + await feedback.confirm('确定要删除?') + await fileCateDelete({ id }) + cateId.value = '' + getCateLists() + } + + //选中分类 + const handleCatSelect = (item: any) => { + cateId.value = item.id + } + + return { + treeRef, + cateId, + cateLists, + handleAddCate, + handleEditCate, + handleDeleteCate, + getCateLists, + handleCatSelect + } +} + +// 处理文件的钩子函数 +export function useFile( + cateId: Ref , + type: Ref , + limit: Ref , + size: number +) { + const tableRef = shallowRef() + const listShowType = ref('normal') + const moveId = ref(0) + const select = ref ([]) + const isCheckAll = ref(false) + const isIndeterminate = ref(false) + const fileParams = reactive({ + name: '', + type: type, + cid: cateId + }) + const { pager, getLists, resetPage } = usePaging({ + fetchFun: fileList, + params: fileParams, + firstLoading: true, + size + }) + + const getFileList = () => { + getLists() + } + const refresh = () => { + resetPage() + } + + const isSelect = (id: number) => { + return !!select.value.find((item: any) => item.id == id) + } + + const batchFileDelete = async (id?: number[]) => { + await feedback.confirm( + '确认删除后,本地或云存储文件也将同步删除,如文件已被使用,请谨慎操作!' + ) + const ids = id ? id : select.value.map((item: any) => item.id) + await fileDelete({ ids }) + getFileList() + clearSelect() + } + + const batchFileMove = async () => { + const ids = select.value.map((item: any) => item.id) + await fileMove({ ids, cid: moveId.value }) + moveId.value = 0 + getFileList() + clearSelect() + } + + const selectFile = (item: any) => { + const index = select.value.findIndex((items: any) => items.id == item.id) + if (index != -1) { + select.value.splice(index, 1) + return + } + if (select.value.length == limit.value) { + if (limit.value == 1) { + select.value = [] + select.value.push(item) + return + } + ElMessage.warning('已达到选择上限') + return + } + select.value.push(item) + } + + const clearSelect = () => { + select.value = [] + } + + const cancelSelete = (id: number) => { + select.value = select.value.filter((item: any) => item.id != id) + } + + const selectAll = (value: CheckboxValueType) => { + isIndeterminate.value = false + tableRef.value?.toggleAllSelection() + if (value) { + select.value = [...pager.lists] + return + } + clearSelect() + } + + const handleFileRename = async (value: string, id: number) => { + await fileRename({ + id, + name: value + }) + getFileList() + } + return { + listShowType, + tableRef, + moveId, + pager, + fileParams, + select, + isCheckAll, + isIndeterminate, + getFileList, + refresh, + batchFileDelete, + batchFileMove, + selectFile, + isSelect, + clearSelect, + cancelSelete, + selectAll, + handleFileRename + } +} diff --git a/admin/src/components/material/index.vue b/admin/src/components/material/index.vue new file mode 100644 index 0000000..5c9eab8 --- /dev/null +++ b/admin/src/components/material/index.vue @@ -0,0 +1,584 @@ + + + ++ + + + + diff --git a/admin/src/components/material/picker.vue b/admin/src/components/material/picker.vue new file mode 100644 index 0000000..88ef489 --- /dev/null +++ b/admin/src/components/material/picker.vue @@ -0,0 +1,303 @@ + +++++ ++ ++++ + +++ ++ +
+ + + ··· + + ++ + ++ ++++ 命名分组 + +++删除分组 ++++ +添加分组 ++++++++ +本地上传 ++ +本地上传 ++ 删除 + + ++ + +移动 + + ++ 移动文件至 +++ + ++ + + + ++ + + ++ + +++ + ++ + ++++ 当页全选 + +++ ++ + ++
+- +
++ + ++ ++++ + +++ +重命名 ++ 查看 + ++ + ++ + ++ + + + ++ + + + ++ {{ row.name }} + + ++ + + ++++ +重命名 ++++ 查看 + +++ ++ 删除 + ++ 暂无数据~ +++++++ 已选择 {{ select.length }} + /{{ limit }} ++清空 ++++ ++
+- +
++++ ++ + ++ + + + + diff --git a/admin/src/components/material/preview.vue b/admin/src/components/material/preview.vue new file mode 100644 index 0000000..6accaa0 --- /dev/null +++ b/admin/src/components/material/preview.vue @@ -0,0 +1,72 @@ + ++ + +++ ++ + +++ ++ ++ + 修改 + | + 查看 +++++ ++++ 添加 + + ++++ + ++ + + diff --git a/admin/src/components/overflow-tooltip/index.vue b/admin/src/components/overflow-tooltip/index.vue new file mode 100644 index 0000000..57734db --- /dev/null +++ b/admin/src/components/overflow-tooltip/index.vue @@ -0,0 +1,47 @@ + ++++ +++ ++ ++ + + + + diff --git a/admin/src/components/pagination/index.vue b/admin/src/components/pagination/index.vue new file mode 100644 index 0000000..020d304 --- /dev/null +++ b/admin/src/components/pagination/index.vue @@ -0,0 +1,50 @@ + ++ ++ {{ content }} ++++ + + diff --git a/admin/src/components/popover-input/index.vue b/admin/src/components/popover-input/index.vue new file mode 100644 index 0000000..88ba84a --- /dev/null +++ b/admin/src/components/popover-input/index.vue @@ -0,0 +1,130 @@ + ++ ++ + + + + diff --git a/admin/src/components/popup/index.vue b/admin/src/components/popup/index.vue new file mode 100644 index 0000000..f8683be --- /dev/null +++ b/admin/src/components/popup/index.vue @@ -0,0 +1,133 @@ + ++ +++ ++++ ++ + ++取消 +确定 +++ ++ ++ + + + + diff --git a/admin/src/components/upload/index.vue b/admin/src/components/upload/index.vue new file mode 100644 index 0000000..8f4a5e9 --- /dev/null +++ b/admin/src/components/upload/index.vue @@ -0,0 +1,148 @@ + ++ +++ + + {{ title }} + + + +{{ content }} + + + + +++ + + + + diff --git a/admin/src/components/video-player/index.vue b/admin/src/components/video-player/index.vue new file mode 100644 index 0000000..fdf64e3 --- /dev/null +++ b/admin/src/components/video-player/index.vue @@ -0,0 +1,72 @@ + ++ ++ + ++ ++++ +{{ item.name }}++++ ++ + + diff --git a/admin/src/config/index.ts b/admin/src/config/index.ts new file mode 100644 index 0000000..926d4ff --- /dev/null +++ b/admin/src/config/index.ts @@ -0,0 +1,10 @@ +const config = { + terminal: 1, //终端 + title: '后台管理系统', //网站默认标题 + version: '1.3.3', //版本号 + baseUrl: `${import.meta.env.VITE_APP_BASE_URL || ''}/`, //请求接口域名 + urlPrefix: 'api', //请求默认前缀 + timeout: 60 * 1000 //请求超时时长 +} + +export default config diff --git a/admin/src/config/setting.ts b/admin/src/config/setting.ts new file mode 100644 index 0000000..3925433 --- /dev/null +++ b/admin/src/config/setting.ts @@ -0,0 +1,18 @@ +const defaultSetting = { + showCrumb: true, // 是否显示面包屑 + showLogo: true, // 是否显示logo + isUniqueOpened: false, //只展开一个一级菜单 + sideWidth: 200, //侧边栏宽度 + sideTheme: 'light', //侧边栏主题 + sideDarkColor: '#1d2124', //侧边栏深色主题颜色 + openMultipleTabs: true, // 是否开启多标签tab栏 + theme: '#4A5DFF', //主题色 + successTheme: '#67c23a', //成功主题色 + warningTheme: '#e6a23c', //警告主题色 + dangerTheme: '#f56c6c', //危险主题色 + errorTheme: '#f56c6c', //错误主题色 + infoTheme: '#909399' //信息主题色 +} +//以上各种主题色分别对应element-plus的几种行为主题 + +export default defaultSetting diff --git a/admin/src/enums/appEnums.ts b/admin/src/enums/appEnums.ts new file mode 100644 index 0000000..90ac145 --- /dev/null +++ b/admin/src/enums/appEnums.ts @@ -0,0 +1,40 @@ +//菜单主题类型 +export enum ThemeEnum { + LIGHT = 'light', + DARK = 'dark' +} + +// 菜单类型 +export enum MenuEnum { + CATALOGUE = 'M', + MENU = 'C', + BUTTON = 'A' +} + +// 屏幕 +export enum ScreenEnum { + SM = 640, + MD = 768, + LG = 1024, + XL = 1280, + '2XL' = 1536 +} + +// 客户端类型 +export enum ClientEnum { + MP_WEIXIN = 1, // 微信-小程序 + OA_WEIXIN = 2, // 微信-公众号 + H5 = 3, // H5 + PC = 4, // PC + IOS = 5, //苹果 + ANDROID = 6 //安卓 +} + +export const ClientMap = { + [ClientEnum.MP_WEIXIN]: '微信小程序', + [ClientEnum.OA_WEIXIN]: '微信公众号', + [ClientEnum.H5]: '手机H5', + [ClientEnum.PC]: '电脑PC', + [ClientEnum.IOS]: '苹果APP', + [ClientEnum.ANDROID]: '安卓APP' +} diff --git a/admin/src/enums/cacheEnums.ts b/admin/src/enums/cacheEnums.ts new file mode 100644 index 0000000..45624e4 --- /dev/null +++ b/admin/src/enums/cacheEnums.ts @@ -0,0 +1,8 @@ +// 本地缓冲key + +//token +export const TOKEN_KEY = 'token' +//账号 +export const ACCOUNT_KEY = 'account' +//设置 +export const SETTING_KEY = 'setting' diff --git a/admin/src/enums/pageEnum.ts b/admin/src/enums/pageEnum.ts new file mode 100644 index 0000000..89e6525 --- /dev/null +++ b/admin/src/enums/pageEnum.ts @@ -0,0 +1,9 @@ +export enum PageEnum { + //登录页面 + LOGIN = '/login', + //无权限页面 + ERROR_403 = '/403', + // 404 + ERROR_404 = '/:pathMatch(.*)*', + INDEX = '/' +} diff --git a/admin/src/enums/requestEnums.ts b/admin/src/enums/requestEnums.ts new file mode 100644 index 0000000..67d30b2 --- /dev/null +++ b/admin/src/enums/requestEnums.ts @@ -0,0 +1,28 @@ +export enum ContentTypeEnum { + // json + JSON = 'application/json;charset=UTF-8', + // form-data 上传资源(图片,视频) + FORM_DATA = 'multipart/form-data;charset=UTF-8' +} + +export enum RequestMethodsEnum { + GET = 'GET', + POST = 'POST' +} + +export enum RequestCodeEnum { + SUCCESS = 200, //成功 + FAILED = 300, // 失败 + PARAMS_VALID_ERROR = 310, //参数校验错误 + PARAMS_TYPE_ERROR = 311, //参数类型错误 + REQUEST_METHOD_ERROR = 312, //请求方法错误 + ASSERT_ARGUMENT_ERROR = 313, //断言参数错误 + ASSERT_MYBATIS_ERROR = 314, //断言mybatis错误 + LOGIN_ACCOUNT_ERROR = 330, //登陆账号或密码错误 + LOGIN_DISABLE_ERROR = 331, //登陆账号已被禁用 + TOKEN_EMPTY = 332, // TOKEN参数为空 + TOKEN_INVALID = 333, // TOKEN参数无效 + NO_PERMISSTION = 403, //无相关权限 + REQUEST_404_ERROR = 404, //请求接口不存在 + SYSTEM_ERROR = 500 //系统错误 +} diff --git a/admin/src/hooks/useDictOptions.ts b/admin/src/hooks/useDictOptions.ts new file mode 100644 index 0000000..30f5c11 --- /dev/null +++ b/admin/src/hooks/useDictOptions.ts @@ -0,0 +1,69 @@ +import { dictDataAll } from '@/api/setting/dict' +import { reactive, toRaw } from 'vue' + +interface Options { + [propName: string]: { + api: PromiseFun + params?: Record+ + transformData?(data: any): any + } +} + +// { +// dict: { +// api: dictData, +// params: { name: 'user' }, +// transformData(data: any) { +// return data.list +// } +// } +// } + +export function useDictOptions (options: Options) { + const optionsData: any = reactive({}) + const optionsKey = Object.keys(options) + const apiLists = optionsKey.map((key) => { + const value = options[key] + optionsData[key] = [] + return () => value.api(toRaw(value.params) || {}) + }) + + const refresh = async () => { + const res = await Promise.allSettled >(apiLists.map((api) => api())) + res.forEach((item, index) => { + const key = optionsKey[index] + if (item.status == 'fulfilled') { + const { transformData } = options[key] + const data = transformData ? transformData(item.value) : item.value + optionsData[key] = data + } + }) + } + refresh() + return { + optionsData: optionsData as T, + refresh + } +} + +// useDictOptions<{ +// dict: any[] +// }>({ +// dict: dictData +// }) + +export function useDictData (dict: string[]) { + const options: Options = {} + for (const type of dict) { + options[type] = { + api: dictDataAll, + params: { + dictType: type + } + } + } + const { optionsData } = useDictOptions (options) + return { + dictData: optionsData + } +} diff --git a/admin/src/hooks/useLockFn.ts b/admin/src/hooks/useLockFn.ts new file mode 100644 index 0000000..c4423d6 --- /dev/null +++ b/admin/src/hooks/useLockFn.ts @@ -0,0 +1,21 @@ +import { ref } from 'vue' + +export function useLockFn(fn: (...args: any[]) => Promise ) { + const isLock = ref(false) + const lockFn = async (...args: any[]) => { + if (isLock.value) return + isLock.value = true + try { + const res = await fn(...args) + isLock.value = false + return res + } catch (e) { + isLock.value = false + throw e + } + } + return { + isLock, + lockFn + } +} diff --git a/admin/src/hooks/useMultipleTabs.ts b/admin/src/hooks/useMultipleTabs.ts new file mode 100644 index 0000000..02e1e43 --- /dev/null +++ b/admin/src/hooks/useMultipleTabs.ts @@ -0,0 +1,47 @@ +import useTabsStore from '@/stores/modules/multipleTabs' +import useSettingStore from '@/stores/modules/setting' + +export default function useMultipleTabs() { + const router = useRouter() + const route = useRoute() + const tabsStore = useTabsStore() + const settingStore = useSettingStore() + + const tabsLists = computed(() => { + return tabsStore.getTabList + }) + + const currentTab = computed(() => { + return route.fullPath + }) + + const addTab = () => { + if (!settingStore.openMultipleTabs) return + tabsStore.addTab(router) + } + + const removeTab = (fullPath?: any) => { + if (!settingStore.openMultipleTabs) return + fullPath = fullPath ?? route.fullPath + tabsStore.removeTab(fullPath, router) + } + + const removeOtherTab = () => { + if (!settingStore.openMultipleTabs) return + tabsStore.removeOtherTab(route) + } + + const removeAllTab = () => { + if (!settingStore.openMultipleTabs) return + tabsStore.removeAllTab(router) + } + + return { + tabsLists, + currentTab, + addTab, + removeTab, + removeOtherTab, + removeAllTab + } +} diff --git a/admin/src/hooks/usePaging.ts b/admin/src/hooks/usePaging.ts new file mode 100644 index 0000000..8a1aa59 --- /dev/null +++ b/admin/src/hooks/usePaging.ts @@ -0,0 +1,62 @@ +import { reactive, toRaw } from 'vue' + +// 分页钩子函数 +interface Options { + page?: number + size?: number + fetchFun: (_arg: any) => Promise + params?: Record + firstLoading?: boolean +} + +export function usePaging(options: Options) { + const { page = 1, size = 15, fetchFun, params = {}, firstLoading = false } = options + // 记录分页初始参数 + const paramsInit: Record = Object.assign({}, toRaw(params)) + // 分页数据 + const pager = reactive({ + page, + size, + loading: firstLoading, + count: 0, + lists: [] as any[] + }) + // 请求分页接口 + const getLists = () => { + pager.loading = true + return fetchFun({ + pageNo: pager.page, + pageSize: pager.size, + ...params + }) + .then((res: any) => { + pager.count = res?.count + pager.lists = res?.lists + return Promise.resolve(res) + }) + .catch((err: any) => { + return Promise.reject(err) + }) + .finally(() => { + pager.loading = false + }) + } + // 重置为第一页 + const resetPage = () => { + pager.page = 1 + getLists() + } + // 重置参数 + const resetParams = () => { + Object.keys(paramsInit).forEach((item) => { + params[item] = paramsInit[item] + }) + getLists() + } + return { + pager, + getLists, + resetParams, + resetPage + } +} diff --git a/admin/src/hooks/useWatchRoute.ts b/admin/src/hooks/useWatchRoute.ts new file mode 100644 index 0000000..381b2ec --- /dev/null +++ b/admin/src/hooks/useWatchRoute.ts @@ -0,0 +1,17 @@ +import type { RouteLocationNormalizedLoaded } from 'vue-router' + +export function useWatchRoute(callback: (route: RouteLocationNormalizedLoaded) => void) { + const route = useRoute() + watch( + route, + () => { + callback(route) + }, + { + immediate: true + } + ) + return { + route + } +} diff --git a/admin/src/install/directives/copy.ts b/admin/src/install/directives/copy.ts new file mode 100644 index 0000000..c0b1909 --- /dev/null +++ b/admin/src/install/directives/copy.ts @@ -0,0 +1,28 @@ +/** + * perm 操作权限处理 + * 指令用法: + * 编辑 + */ + +import feedback from '@/utils/feedback' +import useClipboard from 'vue-clipboard3' +const clipboard = 'data-clipboard-text' +export default { + mounted: (el: HTMLElement, binding: any) => { + el.setAttribute(clipboard, binding.value) + const { toClipboard } = useClipboard() + + el.onclick = () => { + toClipboard(el.getAttribute(clipboard)!) + .then(() => { + feedback.msgSuccess('复制成功') + }) + .catch(() => { + feedback.msgError('复制失败') + }) + } + }, + updated: (el: HTMLElement, binding: any) => { + el.setAttribute(clipboard, binding.value) + } +} diff --git a/admin/src/install/directives/perms.ts b/admin/src/install/directives/perms.ts new file mode 100644 index 0000000..e62765f --- /dev/null +++ b/admin/src/install/directives/perms.ts @@ -0,0 +1,28 @@ +/** + * perm 操作权限处理 + * 指令用法: + *编辑 + */ + +import useUserStore from '@/stores/modules/user' +export default { + mounted: (el: HTMLElement, binding: any) => { + const { value } = binding + const userStore = useUserStore() + const permissions = userStore.perms + const all_permission = '*' + if (Array.isArray(value)) { + if (value.length > 0) { + const hasPermission = permissions.some((key: string) => { + return all_permission == key || value.includes(key) + }) + + if (!hasPermission) { + el.parentNode && el.parentNode.removeChild(el) + } + } + } else { + throw new Error('like v-perms="[\'auth.menu/edit\']"') + } + } +} diff --git a/admin/src/install/index.ts b/admin/src/install/index.ts new file mode 100644 index 0000000..eab8563 --- /dev/null +++ b/admin/src/install/index.ts @@ -0,0 +1,27 @@ +import type { App } from 'vue' +const modules = import.meta.glob('./**/*', { eager: true }) + +// 安装方法,执行某一类相同操作 +function install(app: App) { + Object.keys(modules).forEach((key) => { + const name = key.replace(/(.*\/)*([^.]+).*/gi, '$2') + const type = key.replace(/^\.\/([\w-]+).*/gi, '$1') + const module: any = modules[key] + if (module.default) { + switch (type) { + // 用于注册全局指令 + case 'directives': + app.directive(name, module.default) + break + // 使用插件 + case 'plugins': + typeof module.default === 'function' && module.default(app) + break + } + } + }) +} + +export default { + install +} diff --git a/admin/src/install/plugins/element.ts b/admin/src/install/plugins/element.ts new file mode 100644 index 0000000..ac6ae23 --- /dev/null +++ b/admin/src/install/plugins/element.ts @@ -0,0 +1,11 @@ +import * as ElementPlusIcons from '@element-plus/icons-vue' +import type { App } from 'vue' +//https://github.com/element-plus/element-plus/issues/7293 +import 'element-plus/es/components/dialog/style/css' + +export default (app: App ) => { + // 全局注册ElementPlus图标 + for (const [key, component] of Object.entries(ElementPlusIcons)) { + app.component(key, component) + } +} diff --git a/admin/src/install/plugins/pinia.ts b/admin/src/install/plugins/pinia.ts new file mode 100644 index 0000000..6e72e75 --- /dev/null +++ b/admin/src/install/plugins/pinia.ts @@ -0,0 +1,6 @@ +import store from '@/stores' +import type { App } from 'vue' + +export default (app: App ) => { + app.use(store) +} diff --git a/admin/src/install/plugins/router.ts b/admin/src/install/plugins/router.ts new file mode 100644 index 0000000..4e3647d --- /dev/null +++ b/admin/src/install/plugins/router.ts @@ -0,0 +1,6 @@ +import router from '@/router' +import type { App } from 'vue' + +export default (app: App ) => { + app.use(router) +} diff --git a/admin/src/layout/Empty.vue b/admin/src/layout/Empty.vue new file mode 100644 index 0000000..069271f --- /dev/null +++ b/admin/src/layout/Empty.vue @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/admin/src/layout/components/footer.vue b/admin/src/layout/components/footer.vue new file mode 100644 index 0000000..0dcf06b --- /dev/null +++ b/admin/src/layout/components/footer.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/admin/src/layout/default/components/header/breadcrumb.vue b/admin/src/layout/default/components/header/breadcrumb.vue new file mode 100644 index 0000000..58c8ef2 --- /dev/null +++ b/admin/src/layout/default/components/header/breadcrumb.vue @@ -0,0 +1,20 @@ + ++ ++ + + + diff --git a/admin/src/layout/default/components/header/fold.vue b/admin/src/layout/default/components/header/fold.vue new file mode 100644 index 0000000..7ce9360 --- /dev/null +++ b/admin/src/layout/default/components/header/fold.vue @@ -0,0 +1,15 @@ + ++ {{ item.meta.title }} + +++ + + diff --git a/admin/src/layout/default/components/header/full-screen.vue b/admin/src/layout/default/components/header/full-screen.vue new file mode 100644 index 0000000..96cec1e --- /dev/null +++ b/admin/src/layout/default/components/header/full-screen.vue @@ -0,0 +1,10 @@ + ++ ++ + + diff --git a/admin/src/layout/default/components/header/index.vue b/admin/src/layout/default/components/header/index.vue new file mode 100644 index 0000000..3f6d839 --- /dev/null +++ b/admin/src/layout/default/components/header/index.vue @@ -0,0 +1,55 @@ + ++ + + + + + + diff --git a/admin/src/layout/default/components/header/multiple-tabs.vue b/admin/src/layout/default/components/header/multiple-tabs.vue new file mode 100644 index 0000000..16a5d48 --- /dev/null +++ b/admin/src/layout/default/components/header/multiple-tabs.vue @@ -0,0 +1,122 @@ + + ++ ++ + + + diff --git a/admin/src/layout/default/components/header/refresh.vue b/admin/src/layout/default/components/header/refresh.vue new file mode 100644 index 0000000..a55ccba --- /dev/null +++ b/admin/src/layout/default/components/header/refresh.vue @@ -0,0 +1,14 @@ + ++++ + ++ + + + ++ + + + + +关闭当前 +关闭其他 +关闭全部 +++ + + diff --git a/admin/src/layout/default/components/header/user-drop-down.vue b/admin/src/layout/default/components/header/user-drop-down.vue new file mode 100644 index 0000000..3970709 --- /dev/null +++ b/admin/src/layout/default/components/header/user-drop-down.vue @@ -0,0 +1,34 @@ + ++ + + + + diff --git a/admin/src/layout/default/components/main.vue b/admin/src/layout/default/components/main.vue new file mode 100644 index 0000000..b5386b3 --- /dev/null +++ b/admin/src/layout/default/components/main.vue @@ -0,0 +1,26 @@ + +++ + ++ {{ userInfo.nickname }}++ + + ++ +个人设置 +退出登录 ++ + + + + + diff --git a/admin/src/layout/default/components/setting/drawer.vue b/admin/src/layout/default/components/setting/drawer.vue new file mode 100644 index 0000000..efd08fd --- /dev/null +++ b/admin/src/layout/default/components/setting/drawer.vue @@ -0,0 +1,220 @@ + ++ ++++ ++ ++ ++ + + + + diff --git a/admin/src/layout/default/components/setting/index.vue b/admin/src/layout/default/components/setting/index.vue new file mode 100644 index 0000000..d0f4957 --- /dev/null +++ b/admin/src/layout/default/components/setting/index.vue @@ -0,0 +1,19 @@ + ++ ++ 风格设置 +++++++
+ + 主题颜色 +++++ + 开启黑暗模式 +++++ + 开启多页签栏 +++++ + 只展开一个一级菜单 +++++ ++菜单栏宽度++++ ++显示LOGO++++ ++显示面包屑++++ ++重置主题 +++ + + diff --git a/admin/src/layout/default/components/sidebar/index.vue b/admin/src/layout/default/components/sidebar/index.vue new file mode 100644 index 0000000..28413a0 --- /dev/null +++ b/admin/src/layout/default/components/sidebar/index.vue @@ -0,0 +1,44 @@ + + + + + + + diff --git a/admin/src/layout/default/components/sidebar/logo.vue b/admin/src/layout/default/components/sidebar/logo.vue new file mode 100644 index 0000000..f469f4e --- /dev/null +++ b/admin/src/layout/default/components/sidebar/logo.vue @@ -0,0 +1,61 @@ + ++ + ++ + + + diff --git a/admin/src/layout/default/components/sidebar/menu-item.vue b/admin/src/layout/default/components/sidebar/menu-item.vue new file mode 100644 index 0000000..09b46aa --- /dev/null +++ b/admin/src/layout/default/components/sidebar/menu-item.vue @@ -0,0 +1,87 @@ + + ++ + ++++ ++ ++ ++ + {{ routeMeta?.title }} + + + + + + + + + diff --git a/admin/src/layout/default/components/sidebar/menu.vue b/admin/src/layout/default/components/sidebar/menu.vue new file mode 100644 index 0000000..7efba48 --- /dev/null +++ b/admin/src/layout/default/components/sidebar/menu.vue @@ -0,0 +1,101 @@ + + + + + + + diff --git a/admin/src/layout/default/components/sidebar/side.vue b/admin/src/layout/default/components/sidebar/side.vue new file mode 100644 index 0000000..974dfef --- /dev/null +++ b/admin/src/layout/default/components/sidebar/side.vue @@ -0,0 +1,66 @@ + ++ {{ routeMeta?.title }} + + + ++ + + + + diff --git a/admin/src/layout/default/index.vue b/admin/src/layout/default/index.vue new file mode 100644 index 0000000..ba6eb38 --- /dev/null +++ b/admin/src/layout/default/index.vue @@ -0,0 +1,22 @@ + ++ + ++ + + diff --git a/admin/src/main.ts b/admin/src/main.ts new file mode 100644 index 0000000..79cae31 --- /dev/null +++ b/admin/src/main.ts @@ -0,0 +1,10 @@ +import { createApp } from 'vue' +import App from './App.vue' +import install from './install' +import './permission' +import './styles/index.scss' +import 'virtual:svg-icons-register' + +const app = createApp(App) +app.use(install) +app.mount('#app') diff --git a/admin/src/permission.ts b/admin/src/permission.ts new file mode 100644 index 0000000..ca7017a --- /dev/null +++ b/admin/src/permission.ts @@ -0,0 +1,84 @@ +/** + * 权限控制 + */ + +import NProgress from 'nprogress' +import router, { findFirstValidRoute } from './router' +import 'nprogress/nprogress.css' +import { isExternal } from './utils/validate' +import useUserStore from './stores/modules/user' +import { INDEX_ROUTE, INDEX_ROUTE_NAME } from './router/routes' +import { PageEnum } from './enums/pageEnum' +import useTabsStore from './stores/modules/multipleTabs' +import { clearAuthInfo } from './utils/auth' +import config from './config' + +// NProgress配置 +NProgress.configure({ showSpinner: false }) + +const loginPath = PageEnum.LOGIN +const defaultPath = PageEnum.INDEX +// 免登录白名单 +const whiteList: string[] = [PageEnum.LOGIN, PageEnum.ERROR_403] +router.beforeEach(async (to, from, next) => { + // 开始 Progress Bar + NProgress.start() + document.title = to.meta.title ?? config.title + const userStore = useUserStore() + const tabsStore = useTabsStore() + if (whiteList.includes(to.path)) { + // 在免登录白名单,直接进入 + next() + } else if (userStore.token) { + // 获取用户信息 + const hasGetUserInfo = Object.keys(userStore.userInfo).length !== 0 + if (hasGetUserInfo) { + if (to.path === loginPath) { + next({ path: defaultPath }) + } else { + next() + } + } else { + try { + await userStore.getUserInfo() + await userStore.getMenu() + const routes = userStore.routes + // 找到第一个有效路由 + const routeName = findFirstValidRoute(routes) + // 没有有效路由跳转到403页面 + if (!routeName) { + clearAuthInfo() + next(PageEnum.ERROR_403) + return + } + tabsStore.setRouteName(routeName!) + INDEX_ROUTE.redirect = { name: routeName } + + // 动态添加index路由 + router.addRoute(INDEX_ROUTE) + routes.forEach((route: any) => { + // https 则不插入 + if (isExternal(route.path)) { + return + } + if (!route.children) { + router.addRoute(INDEX_ROUTE_NAME, route) + return + } + // 动态添加可访问路由表 + router.addRoute(route) + }) + next({ ...to, replace: true }) + } catch (err) { + clearAuthInfo() + next({ path: loginPath, query: { redirect: to.fullPath } }) + } + } + } else { + next({ path: loginPath, query: { redirect: to.fullPath } }) + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/admin/src/router/index.ts b/admin/src/router/index.ts new file mode 100644 index 0000000..f4dd998 --- /dev/null +++ b/admin/src/router/index.ts @@ -0,0 +1,110 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { MenuEnum } from '@/enums/appEnums' +import { isExternal } from '@/utils/validate' +import { constantRoutes, INDEX_ROUTE_NAME, LAYOUT, Empty } from './routes' +import useUserStore from '@/stores/modules/user' + +// 匹配views里面所有的.vue文件,动态引入 +const modules = import.meta.glob('/src/views/**/*.vue') + +// +export function getModulesKey() { + return Object.keys(modules).map((item) => item.replace('/src/views/', '').replace('.vue', '')) +} + +// 过滤路由所需要的数据 +export function filterAsyncRoutes(routes: any[], firstRoute = true) { + return routes.map((route) => { + const routeRecord = createRouteRecord(route, firstRoute) + if (route.children != null && route.children && route.children.length) { + routeRecord.children = filterAsyncRoutes(route.children, false) + } + return routeRecord + }) +} + +// 创建一条路由记录 +export function createRouteRecord(route: any, firstRoute: boolean): RouteRecordRaw { + //@ts-ignore + const routeRecord: RouteRecordRaw = { + path: isExternal(route.paths) ? route.paths : firstRoute ? `/${route.paths}` : route.paths, + name: Symbol(route.paths), + meta: { + hidden: !route.isShow, + keepAlive: !!route.isCache, + title: route.menuName, + perms: route.perms, + query: route.params, + icon: route.menuIcon, + type: route.menuType, + activeMenu: route.selected + } + } + switch (route.menuType) { + case MenuEnum.CATALOGUE: + routeRecord.component = firstRoute ? LAYOUT : Empty + if (!route.children) { + routeRecord.component = Empty + } + break + case MenuEnum.MENU: + routeRecord.component = loadRouteView(route.component) + break + } + return routeRecord +} + +// 动态加载组件 +export function loadRouteView(component: string) { + try { + const key = Object.keys(modules).find((key) => { + return key.includes(`${component}.vue`) + }) + if (key) { + return modules[key] + } + throw Error(`找不到组件${component},请确保组件路径正确`) + } catch (error) { + console.error(error) + return Empty + } +} + +// 找到第一个有效的路由 +export function findFirstValidRoute(routes: RouteRecordRaw[]): string | undefined { + for (const route of routes) { + if (route.meta?.type == MenuEnum.MENU && !route.meta?.hidden && !isExternal(route.path)) { + return route.name as string + } + if (route.children) { + const name = findFirstValidRoute(route.children) + if (name) { + return name + } + } + } +} +//通过权限字符查询路由路径 +export function getRoutePath(perms: string) { + const routerObj = useRouter() || router + return routerObj.getRoutes().find((item) => item.meta?.perms == perms)?.path || '' +} + +// 重置路由 +export function resetRouter() { + router.removeRoute(INDEX_ROUTE_NAME) + const { routes } = useUserStore() + routes.forEach((route) => { + const name = route.name + if (name && router.hasRoute(name)) { + router.removeRoute(name) + } + }) +} + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: constantRoutes +}) + +export default router diff --git a/admin/src/router/routes.ts b/admin/src/router/routes.ts new file mode 100644 index 0000000..988f1aa --- /dev/null +++ b/admin/src/router/routes.ts @@ -0,0 +1,59 @@ +/** + * Note: 路由配置项 + * + * path: '/path' // 路由路径 + * name:'router-name' // 设定路由的名字,一定要填写不然使用++ ++ +++++ +++ 时会出现各种问题 + * meta : { + title: 'title' // 设置该路由在侧边栏的名字 + icon: 'icon-name' // 设置该路由的图标 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + query: '{"id": 1}' // 访问路由的默认传递参数 + hidden: true // 当设置 true 的时候该路由不会在侧边栏出现 + hideTab: true //当设置 true 的时候该路由不会在多标签tab栏出现 + } + */ + +import type { RouteRecordRaw } from 'vue-router' +import { PageEnum } from '@/enums/pageEnum' +// import Layout from '@/layout/default/index.vue' +// import Empty from '@/layout/Empty.vue' + +export const LAYOUT = () => import('@/layout/default/index.vue') // () => Promise.resolve(Layout) +export const Empty = () => import('@/layout/Empty.vue') + +export const INDEX_ROUTE_NAME = 'INDEX_ROUTE' + +export const constantRoutes: Array = [ + { + path: PageEnum.ERROR_404, + component: () => import('@/views/error/404.vue') + }, + { + path: PageEnum.ERROR_403, + component: () => import('@/views/error/403.vue') + }, + { + path: PageEnum.LOGIN, + component: () => import('@/views/account/login.vue') + }, + { + path: '/user', + component: LAYOUT, + children: [ + { + path: 'setting', + name: Symbol(), + component: () => import('@/views/user/setting.vue'), + meta: { + title: '个人设置' + } + } + ] + } +] + +export const INDEX_ROUTE: RouteRecordRaw = { + path: PageEnum.INDEX, + component: LAYOUT, + name: INDEX_ROUTE_NAME +} diff --git a/admin/src/stores/index.ts b/admin/src/stores/index.ts new file mode 100644 index 0000000..7c7ea69 --- /dev/null +++ b/admin/src/stores/index.ts @@ -0,0 +1,3 @@ +import { createPinia } from 'pinia' +const store = createPinia() +export default store diff --git a/admin/src/stores/modules/app.ts b/admin/src/stores/modules/app.ts new file mode 100644 index 0000000..f51e1f7 --- /dev/null +++ b/admin/src/stores/modules/app.ts @@ -0,0 +1,51 @@ +import { getConfig } from '@/api/app' +import { defineStore } from 'pinia' +interface AppSate { + config: Record + isMobile: boolean + isCollapsed: boolean + isRouteShow: boolean +} + +const useAppStore = defineStore({ + id: 'app', + state: (): AppSate => { + return { + config: {}, + isMobile: true, + isCollapsed: false, + isRouteShow: true + } + }, + actions: { + getImageUrl(url: string) { + return url ? `${this.config.ossDomain}${url}` : '' + }, + getConfig() { + return new Promise((resolve, reject) => { + getConfig() + .then((data) => { + this.config = data + resolve(data) + }) + .catch((err) => { + reject(err) + }) + }) + }, + setMobile(value: boolean) { + this.isMobile = value + }, + toggleCollapsed(toggle?: boolean) { + this.isCollapsed = toggle ?? !this.isCollapsed + }, + refreshView() { + this.isRouteShow = false + nextTick(() => { + this.isRouteShow = true + }) + } + } +}) + +export default useAppStore diff --git a/admin/src/stores/modules/multipleTabs.ts b/admin/src/stores/modules/multipleTabs.ts new file mode 100644 index 0000000..b14e366 --- /dev/null +++ b/admin/src/stores/modules/multipleTabs.ts @@ -0,0 +1,169 @@ +import { defineStore } from 'pinia' +import { isExternal } from '@/utils/validate' +import type { + LocationQuery, + RouteLocationNormalized, + RouteParamsRaw, + Router, + RouteRecordName +} from 'vue-router' +import { PageEnum } from '@/enums/pageEnum' + +interface TabItem { + name: RouteRecordName + fullPath: string + path: string + title?: string + query?: LocationQuery + params?: RouteParamsRaw +} + +interface TabsSate { + cacheTabList: Set + tabList: TabItem[] + tasMap: Record + indexRouteName: RouteRecordName +} + +const getHasTabIndex = (fullPath: string, tabList: TabItem[]) => { + return tabList.findIndex((item) => item.fullPath == fullPath) +} + +const isCannotAddRoute = (route: RouteLocationNormalized, router: Router) => { + const { path, meta, name } = route + if (!path || isExternal(path)) return true + if (meta?.hideTab) return true + if (!router.hasRoute(name!)) return true + if (([PageEnum.LOGIN, PageEnum.ERROR_403] as string[]).includes(path)) { + return true + } + return false +} + +const findTabsIndex = (fullPath: string, tabList: TabItem[]) => { + return tabList.findIndex((item) => item.fullPath === fullPath) +} + +const getComponentName = (route: RouteLocationNormalized) => { + return route.matched.at(-1)?.components?.default?.name +} + +export const getRouteParams = (tabItem: TabItem) => { + const { params, path, query } = tabItem + return { + params: params || {}, + path, + query: query || {} + } +} + +const useTabsStore = defineStore({ + id: 'tabs', + state: (): TabsSate => ({ + cacheTabList: new Set(), + tabList: [], + tasMap: {}, + indexRouteName: '' + }), + getters: { + getTabList(): TabItem[] { + return this.tabList + }, + getCacheTabList(): string[] { + return Array.from(this.cacheTabList) + } + }, + actions: { + setRouteName(name: RouteRecordName) { + this.indexRouteName = name + }, + addCache(componentName?: string) { + if (componentName) this.cacheTabList.add(componentName) + }, + removeCache(componentName?: string) { + if (componentName && this.cacheTabList.has(componentName)) { + this.cacheTabList.delete(componentName) + } + console.log(this.cacheTabList) + }, + clearCache() { + this.cacheTabList.clear() + }, + resetState() { + this.cacheTabList = new Set() + this.tabList = [] + this.tasMap = {} + this.indexRouteName = '' + }, + addTab(router: Router) { + const route = unref(router.currentRoute) + const { name, query, meta, params, fullPath, path } = route + if (isCannotAddRoute(route, router)) return + const hasTabIndex = getHasTabIndex(fullPath!, this.tabList) + const componentName = getComponentName(route) + const tabItem = { + name: name!, + path, + fullPath, + title: meta?.title, + query, + params + } + this.tasMap[fullPath] = tabItem + if (meta?.keepAlive) { + this.addCache(componentName) + } + if (hasTabIndex != -1) { + return + } + + this.tabList.push(tabItem) + }, + removeTab(fullPath: string, router: Router) { + const { currentRoute, push } = router + const index = findTabsIndex(fullPath, this.tabList) + // 移除tab + if (this.tabList.length > 1) { + index !== -1 && this.tabList.splice(index, 1) + } + const componentName = getComponentName(currentRoute.value) + this.removeCache(componentName) + if (fullPath !== currentRoute.value.fullPath) { + return + } + // 删除选中的tab + let toTab: TabItem | null = null + + if (index === 0) { + toTab = this.tabList[index] + } else { + toTab = this.tabList[index - 1] + } + + const toRoute = getRouteParams(toTab) + push(toRoute) + }, + removeOtherTab(route: RouteLocationNormalized) { + this.tabList = this.tabList.filter((item) => item.fullPath == route.fullPath) + const componentName = getComponentName(route) + this.cacheTabList.forEach((name) => { + if (componentName !== name) { + this.removeCache(name) + } + }) + }, + removeAllTab(router: Router) { + const { push, currentRoute } = router + const { name } = unref(currentRoute) + if (name == this.indexRouteName) { + this.removeOtherTab(currentRoute.value) + return + } + this.tabList = [] + this.clearCache() + push(PageEnum.INDEX) + } + } +}) + +export default useTabsStore diff --git a/admin/src/stores/modules/setting.ts b/admin/src/stores/modules/setting.ts new file mode 100644 index 0000000..219a58b --- /dev/null +++ b/admin/src/stores/modules/setting.ts @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia' +import defaultSetting from '@/config/setting' +import cache from '@/utils/cache' +import { isObject } from '@vue/shared' +import { setTheme } from '@/utils/theme' +import { SETTING_KEY } from '@/enums/cacheEnums' +const storageSetting = cache.get(SETTING_KEY) + +export const useSettingStore = defineStore({ + id: 'setting', + state: () => { + const state = { + showDrawer: false, + ...defaultSetting + } + isObject(storageSetting) && Object.assign(state, storageSetting) + return state + }, + actions: { + // 设置布局设置 + setSetting(data: Record ) { + const { key, value } = data + if (this.hasOwnProperty(key)) { + //@ts-ignore + this[key] = value + } + const settings: any = Object.assign({}, this.$state) + delete settings.showDrawer + cache.set(SETTING_KEY, settings) + }, + // 设置主题色 + setTheme(isDark: boolean) { + setTheme( + { + primary: this.theme, + success: this.successTheme, + warning: this.warningTheme, + danger: this.dangerTheme, + error: this.errorTheme, + info: this.infoTheme + }, + isDark + ) + }, + resetTheme() { + for (const key in defaultSetting) { + //@ts-ignore + this[key] = defaultSetting[key] + } + cache.remove(SETTING_KEY) + } + } +}) + +export default useSettingStore diff --git a/admin/src/stores/modules/user.ts b/admin/src/stores/modules/user.ts new file mode 100644 index 0000000..d5248d6 --- /dev/null +++ b/admin/src/stores/modules/user.ts @@ -0,0 +1,96 @@ +import { defineStore } from 'pinia' +import cache from '@/utils/cache' +import type { RouteRecordRaw } from 'vue-router' +import { getUserInfo, login, logout, getMenu } from '@/api/user' +import router, { filterAsyncRoutes } from '@/router' +import { TOKEN_KEY } from '@/enums/cacheEnums' +import { PageEnum } from '@/enums/pageEnum' +import { clearAuthInfo, getToken } from '@/utils/auth' +export interface UserState { + token: string + userInfo: Record + routes: RouteRecordRaw[] + menu: any[] + perms: string[] +} + +const useUserStore = defineStore({ + id: 'user', + state: (): UserState => ({ + token: getToken() || '', + // 用户信息 + userInfo: {}, + // 路由 + routes: [], + menu: [], + // 权限 + perms: [] + }), + getters: {}, + actions: { + resetState() { + this.token = '' + this.userInfo = {} + this.perms = [] + }, + login(playload: any) { + const { account, password } = playload + return new Promise((resolve, reject) => { + login({ + username: account, + password: password + }) + .then((data) => { + this.token = data.token + cache.set(TOKEN_KEY, data.token) + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + }, + logout() { + return new Promise((resolve, reject) => { + logout() + .then(async (data) => { + this.token = '' + await router.push(PageEnum.LOGIN) + clearAuthInfo() + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + }, + getUserInfo() { + return new Promise((resolve, reject) => { + getUserInfo() + .then((data) => { + this.userInfo = data.user + this.perms = data.permissions + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + }, + getMenu() { + return new Promise((resolve, reject) => { + getMenu() + .then((data) => { + this.menu = data + this.routes = filterAsyncRoutes(data) + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + } + } +}) + +export default useUserStore diff --git a/admin/src/styles/dark.css b/admin/src/styles/dark.css new file mode 100644 index 0000000..ae969fd --- /dev/null +++ b/admin/src/styles/dark.css @@ -0,0 +1,49 @@ +:root.dark { + color-scheme: dark; + --table-header-bg-color: var(--el-bg-color); + --el-bg-color-page: #0a0a0a; + --el-bg-color: #1d2124; + --el-bg-color-overlay: #1d1e1f; + --el-text-color-primary: #e5eaf3; + --el-text-color-regular: #cfd3dc; + --el-text-color-secondary: #a3a6ad; + --el-text-color-placeholder: #8d9095; + --el-text-color-disabled: #6c6e72; + --el-border-color-darker: #636466; + --el-border-color-dark: #58585b; + --el-border-color: #4c4d4f; + --el-border-color-light: #414243; + --el-border-color-lighter: #363637; + --el-border-color-extra-light: #2b2b2c; + --el-fill-color-darker: #424243; + --el-fill-color-dark: #39393a; + --el-fill-color: #303030; + --el-fill-color-light: #262727; + --el-fill-color-lighter: #1d1d1d; + --el-fill-color-extra-light: #191919; + --el-fill-color-blank: var(--el-bg-color); + --el-mask-color: rgba(0, 0, 0, 0.8); + --el-mask-color-extra-light: rgba(0, 0, 0, 0.3); + --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.36), 0px 8px 20px rgba(0, 0, 0, 0.72); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.72); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.72); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.72), 0px 12px 32px #000000, + 0px 8px 16px -8px #000000 !important; + /* wangeditor主题 */ + --w-e-textarea-bg-color: var(--el-bg-color); + --w-e-textarea-color: var(--el-text-color-primary); + --w-e-textarea-border-color: var(--el-border-color); + --w-e-textarea-slight-border-color: var(--el-border-color-light); + --w-e-textarea-slight-color: var(--el-border-color); + --w-e-textarea-slight-bg-color: var(--el-bg-color-page); + /* --w-e-textarea-selected-border-color: #b4d5ff; + --w-e-textarea-handler-bg-color: #4290f7; */ + --w-e-toolbar-color: var(--el-text-color-primary); + --w-e-toolbar-bg-color: var(--el-bg-color); + --w-e-toolbar-active-color: var(--el-text-color-primary); + --w-e-toolbar-active-bg-color: var(--el-bg-color); + --w-e-toolbar-disabled-color: var(--el-text-color-disabled); + --w-e-toolbar-border-color: var(--el-border-color); + --w-e-modal-button-bg-color: var(--el-bg-color); + --w-e-modal-button-border-color: var(--el-border-color); +} diff --git a/admin/src/styles/element.scss b/admin/src/styles/element.scss new file mode 100644 index 0000000..07d8e5d --- /dev/null +++ b/admin/src/styles/element.scss @@ -0,0 +1,145 @@ +:root { + // 弹窗居中 + .el-overlay-dialog { + display: flex; + justify-content: center; + align-items: center; + min-height: 100%; + position: static; + + .el-dialog { + --el-dialog-content-font-size: var(--el-font-size-base); + --el-dialog-margin-top: 50px; + max-width: calc(100vw - 30px); + flex: none; + display: flex; + flex-direction: column; + border-radius: 5px; + + &.body-padding .el-dialog__body { + padding: 0; + } + + .el-dialog__body { + flex: 1; + padding: 15px 20px; + } + .el-dialog__header { + font-size: var(--el-font-size-large); + } + } + } + + .el-drawer { + --el-drawer-padding-primary: 16px; + &__header { + margin-bottom: 0; + padding: 13px 16px; + border-bottom: 1px solid var(--el-border-color-lighter); + } + &__title { + @apply text-tx-primary; + } + } + + .el-table { + --el-table-header-text-color: var(--el-text-color-primary); + --el-table-header-bg-color: var(--table-header-bg-color); + font-size: var(--el-font-size-base); + + thead { + th { + font-weight: 400; + } + } + } + + .el-input-group__prepend { + background-color: var(--el-fill-color-blank); + } + + .el-checkbox { + --el-checkbox-font-size: var(--el-font-size-base); + } + + .el-menu--popup-container { + &.theme-light { + .el-menu { + .el-menu-item { + &.is-active { + @apply bg-primary-light-9 border-primary border-r-2; + } + } + .el-menu-item:hover, + .el-sub-menu__title:hover { + color: var(--el-color-primary); + } + } + } + &.theme-dark { + .el-menu { + .el-menu-item { + &.is-active { + @apply bg-primary; + } + } + } + } + } + + .el-message-box { + --el-messagebox-width: 350px; + } + .el-date-editor { + --el-date-editor-width: 280px; + .el-range-input { + font-size: var(--el-font-size-small); + } + } + + .el-button--primary { + --el-button-hover-link-text-color: var(--el-color-primary-light-3); + } + .el-button--success { + --el-button-hover-link-text-color: var(--el-color-success-light-3); + } + .el-button--info { + --el-button-hover-link-text-color: var(--el-color-info-light-3); + } + .el-button--warning { + --el-button-hover-link-text-color: var(--el-color-warning-light-3); + } + .el-button--danger { + --el-button-hover-link-text-color: var(--el-color-danger-light-3); + } + .el-image__error { + font-size: 12px; + } + .el-tabs__nav-wrap::after { + height: 1px; + } +} +@media (max-width: 768px) { + .el-pagination > .el-pagination__jump { + display: none !important; + } + .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-button { + // 防止被tailwindcss默认样式覆盖 + background-color: var(--el-button-bg-color, var(--el-color-white)); + + //覆盖el-button的点击样式 + &:focus { + border-color: var(--el-button-border-color); + background-color: var(--el-button-bg-color); + } + &:hover { + color: var(--el-button-hover-text-color); + border-color: var(--el-button-hover-border-color); + background-color: var(--el-button-hover-bg-color); + } +} diff --git a/admin/src/styles/index.scss b/admin/src/styles/index.scss new file mode 100644 index 0000000..7bd1726 --- /dev/null +++ b/admin/src/styles/index.scss @@ -0,0 +1,6 @@ + +@import 'element.scss'; +@import 'dark.css'; +@import 'var.css'; +@import 'tailwind.css'; +@import 'public.scss'; diff --git a/admin/src/styles/public.scss b/admin/src/styles/public.scss new file mode 100644 index 0000000..a11a8bd --- /dev/null +++ b/admin/src/styles/public.scss @@ -0,0 +1,18 @@ +body { + @apply text-base text-tx-primary overflow-hidden min-w-[375px]; +} +.form-tips { + @apply text-tx-secondary text-xs leading-6 mt-1; +} + +.clearfix:after { + content: ''; + display: block; + clear: both; + visibility: hidden; +} + +/* NProgress */ +#nprogress .bar { + @apply bg-primary #{!important}; +} diff --git a/admin/src/styles/tailwind.css b/admin/src/styles/tailwind.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/admin/src/styles/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/admin/src/styles/var.css b/admin/src/styles/var.css new file mode 100644 index 0000000..a10631a --- /dev/null +++ b/admin/src/styles/var.css @@ -0,0 +1,48 @@ +:root { + --el-font-family: theme(fontFamily.sans); + --el-font-weight-primary: 400; + --el-menu-item-height: 46px; + --el-menu-sub-item-height: var(--el-menu-item-height); + --el-menu-icon-width: 18px; + --aside-width: 200px; + --navbar-height: 50px; + --color-white: #ffffff; + --table-header-bg-color: #f8f8f8; + --el-font-size-extra-large: 18px; + --el-menu-base-level-padding: 16px; + --el-menu-level-padding: 26px; + --el-font-size-large: 16px; + --el-font-size-medium: 15px; + --el-font-size-base: 14px; + --el-font-size-small: 13px; + --el-font-size-extra-small: 12px; + + --el-bg-color: var(--color-white); + --el-bg-color-page: #f6f6f6; + --el-bg-color-overlay: #ffffff; + --el-text-color-primary: #333333; + --el-text-color-regular: #666666; + --el-text-color-secondary: #999999; + --el-text-color-placeholder: #a8abb2; + --el-text-color-disabled: #c0c4cc; + --el-border-color: #dcdfe6; + --el-border-color-light: #e4e7ed; + --el-border-color-lighter: #ebeef5; + --el-border-color-extra-light: #f2f2f2; + --el-border-color-dark: #d4d7de; + --el-border-color-darker: #cdd0d6; + --el-fill-color: #f0f2f5; + --el-fill-color-light: #f8f8f8; + --el-fill-color-lighter: #fafafa; + --el-fill-color-extra-light: #fafcff; + --el-fill-color-dark: #ebedf0; + --el-fill-color-darker: #e6e8eb; + --el-fill-color-blank: #ffffff; + --el-mask-color: rgba(255, 255, 255, 0.9); + --el-mask-color-extra-light: rgba(255, 255, 255, 0.3); + -el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.04), 0px 8px 20px rgba(0, 0, 0, 0.08); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.12); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.12); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12), + 0px 8px 16px -8px rgba(0, 0, 0, 0.16); +} diff --git a/admin/src/utils/auth.ts b/admin/src/utils/auth.ts new file mode 100644 index 0000000..e7a6877 --- /dev/null +++ b/admin/src/utils/auth.ts @@ -0,0 +1,18 @@ +import { TOKEN_KEY } from '@/enums/cacheEnums' +import { resetRouter } from '@/router' +import useTabsStore from '@/stores/modules/multipleTabs' +import useUserStore from '@/stores/modules/user' +import cache from './cache' + +export function getToken() { + return cache.get(TOKEN_KEY) +} + +export function clearAuthInfo() { + const userStore = useUserStore() + const tabsStore = useTabsStore() + userStore.resetState() + tabsStore.$reset() + cache.remove(TOKEN_KEY) + resetRouter() +} diff --git a/admin/src/utils/cache.ts b/admin/src/utils/cache.ts new file mode 100644 index 0000000..d98624f --- /dev/null +++ b/admin/src/utils/cache.ts @@ -0,0 +1,50 @@ +const cache = { + key: 'like_admin_', + //设置缓存(expire为缓存时效) + set(key: string, value: any, expire?: string) { + key = this.getKey(key) + let data: any = { + expire: expire ? this.time() + expire : '', + value + } + + if (typeof data === 'object') { + data = JSON.stringify(data) + } + try { + window.localStorage.setItem(key, data) + } catch (e) { + return null + } + }, + get(key: string) { + key = this.getKey(key) + try { + const data = window.localStorage.getItem(key) + if (!data) { + return null + } + const { value, expire } = JSON.parse(data) + if (expire && expire < this.time()) { + window.localStorage.removeItem(key) + return null + } + return value + } catch (e) { + return null + } + }, + //获取当前时间 + time() { + return Math.round(new Date().getTime() / 1000) + }, + remove(key: string) { + key = this.getKey(key) + window.localStorage.removeItem(key) + }, + getKey(key: string) { + return this.key + key + } +} + +export default cache diff --git a/admin/src/utils/echart.ts b/admin/src/utils/echart.ts new file mode 100644 index 0000000..3d3aee0 --- /dev/null +++ b/admin/src/utils/echart.ts @@ -0,0 +1,65 @@ +//引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。 + +import * as echarts from 'echarts/core' +//引入柱状图图表,图表后缀都为 Chart +import { + BarChart, + LineChart, + PieChart, + MapChart, + PictorialBarChart, + RadarChart, + ScatterChart, + GaugeChart +} from 'echarts/charts' +// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent +} from 'echarts/components' + +//引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步 +import { CanvasRenderer } from 'echarts/renderers' +//标签自动布局,全局过渡动画等特性 +import { LabelLayout, UniversalTransition } from 'echarts/features' + +// 注册必须的组件 +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + RadarChart, + PictorialBarChart, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, + ScatterChart, + CanvasRenderer, + LabelLayout, + UniversalTransition, + GaugeChart +]) diff --git a/admin/src/utils/env.ts b/admin/src/utils/env.ts new file mode 100644 index 0000000..d8951e9 --- /dev/null +++ b/admin/src/utils/env.ts @@ -0,0 +1,13 @@ +/** + * @description: 开发模式 + */ +export function isDevMode(): boolean { + return import.meta.env.DEV +} + +/** + * @description: 生成模式 + */ +export function isProdMode(): boolean { + return import.meta.env.PROD +} diff --git a/admin/src/utils/feedback.ts b/admin/src/utils/feedback.ts new file mode 100644 index 0000000..91672ab --- /dev/null +++ b/admin/src/utils/feedback.ts @@ -0,0 +1,95 @@ +import { + ElMessage, + ElMessageBox, + ElNotification, + ElLoading, + type ElMessageBoxOptions +} from 'element-plus' +import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading' + +export class Feedback { + private loadingInstance: LoadingInstance | null = null + static instance: Feedback | null = null + static getInstance() { + return this.instance ?? (this.instance = new Feedback()) + } + // 消息提示 + msg(msg: string) { + ElMessage.info(msg) + } + // 错误消息 + msgError(msg: string) { + ElMessage.error(msg) + } + // 成功消息 + msgSuccess(msg: string) { + ElMessage.success(msg) + } + // 警告消息 + msgWarning(msg: string) { + ElMessage.warning(msg) + } + // 弹出提示 + alert(msg: string) { + ElMessageBox.alert(msg, '系统提示') + } + // 错误提示 + alertError(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'error' }) + } + // 成功提示 + alertSuccess(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'success' }) + } + // 警告提示 + alertWarning(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'warning' }) + } + // 通知提示 + notify(msg: string) { + ElNotification.info(msg) + } + // 错误通知 + notifyError(msg: string) { + ElNotification.error(msg) + } + // 成功通知 + notifySuccess(msg: string) { + ElNotification.success(msg) + } + // 警告通知 + notifyWarning(msg: string) { + ElNotification.warning(msg) + } + // 确认窗体 + confirm(msg: string) { + return ElMessageBox.confirm(msg, '温馨提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + } + // 提交内容 + prompt(content: string, title: string, options?: ElMessageBoxOptions) { + return ElMessageBox.prompt(content, title, { + confirmButtonText: '确定', + cancelButtonText: '取消', + ...options + }) + } + // 打开全局loading + loading(msg: string) { + this.loadingInstance = ElLoading.service({ + lock: true, + text: msg + }) + } + // 关闭全局loading + closeLoading() { + this.loadingInstance?.close() + } +} + +const feedback = Feedback.getInstance() + +export default feedback diff --git a/admin/src/utils/file.ts b/admin/src/utils/file.ts new file mode 100644 index 0000000..a33a0f3 --- /dev/null +++ b/admin/src/utils/file.ts @@ -0,0 +1,16 @@ +/** + * @description + * @param file + */ +export function streamFileDownload(file: any, fileName = '文件名称.zip') { + const blob = new Blob([file], { type: 'application/octet-stream;charset=UTF-8' }) + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.style.display = 'none' + link.href = url + link.setAttribute('download', fileName) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) // 下载完成移除元素 + window.URL.revokeObjectURL(url) +} diff --git a/admin/src/utils/request/axios.ts b/admin/src/utils/request/axios.ts new file mode 100644 index 0000000..65703e0 --- /dev/null +++ b/admin/src/utils/request/axios.ts @@ -0,0 +1,165 @@ +import { RequestMethodsEnum } from '@/enums/requestEnums' +import axios, { + AxiosError, + type AxiosInstance, + type AxiosRequestConfig, + type AxiosResponse +} from 'axios' +import { isFunction, merge, cloneDeep } from 'lodash' +import axiosCancel from './cancel' +import type { RequestData, RequestOptions } from './type' + +export class Axios { + private axiosInstance: AxiosInstance + private readonly config: AxiosRequestConfig + private readonly options: RequestOptions + constructor(config: AxiosRequestConfig) { + this.config = config + this.options = config.requestOptions + this.axiosInstance = axios.create(config) + this.setupInterceptors() + } + + /** + * @description 获取axios实例 + */ + getAxiosInstance() { + return this.axiosInstance + } + + /** + * @description 设置拦截器 + */ + setupInterceptors() { + if (!this.config.axiosHooks) { + return + } + const { + requestInterceptorsHook, + requestInterceptorsCatchHook, + responseInterceptorsHook, + responseInterceptorsCatchHook + } = this.config.axiosHooks + this.axiosInstance.interceptors.request.use( + (config) => { + this.addCancelToken(config) + if (isFunction(requestInterceptorsHook)) { + config = requestInterceptorsHook(config) + } + return config + }, + (err: Error) => { + if (isFunction(requestInterceptorsCatchHook)) { + requestInterceptorsCatchHook(err) + } + return err + } + ) + this.axiosInstance.interceptors.response.use( + (response: AxiosResponse ) => { + this.removeCancelToken(response.config.url!) + if (isFunction(responseInterceptorsHook)) { + response = responseInterceptorsHook(response) + } + return response + }, + (err: AxiosError) => { + if (isFunction(responseInterceptorsCatchHook)) { + responseInterceptorsCatchHook(err) + } + if (err.code != AxiosError.ERR_CANCELED) { + this.removeCancelToken(err.config?.url!) + } + + if (err.code == AxiosError.ECONNABORTED || err.code == AxiosError.ERR_NETWORK) { + return new Promise((resolve) => setTimeout(resolve, 500)).then(() => + this.retryRequest(err) + ) + } + return Promise.reject(err) + } + ) + } + + /** + * @description 添加CancelToken + */ + addCancelToken(config: AxiosRequestConfig) { + const { ignoreCancelToken } = config.requestOptions + !ignoreCancelToken && axiosCancel.add(config) + } + + /** + * @description 移除CancelToken + */ + removeCancelToken(url: string) { + axiosCancel.remove(url) + } + + /** + * @description 重新请求 + */ + retryRequest(error: AxiosError) { + const config = error.config + const { retryCount, isOpenRetry } = config.requestOptions + if (!isOpenRetry || config.method?.toUpperCase() == RequestMethodsEnum.POST) { + return Promise.reject(error) + } + config.retryCount = config.retryCount ?? 0 + + if (config.retryCount >= retryCount) { + return Promise.reject(error) + } + config.retryCount++ + + return this.axiosInstance.request(config) + } + /** + * @description get请求 + */ + get ( + config: Partial , + options?: Partial + ): Promise { + return this.request({ ...config, method: RequestMethodsEnum.GET }, options) + } + + /** + * @description post请求 + */ + post ( + config: Partial , + options?: Partial + ): Promise { + return this.request({ ...config, method: RequestMethodsEnum.POST }, options) + } + + /** + * @description 请求函数 + */ + request ( + config: Partial , + options?: Partial + ): Promise { + const opt: RequestOptions = merge({}, this.options, options) + const axioxConfig: AxiosRequestConfig = { + ...cloneDeep(config), + requestOptions: opt + } + const { urlPrefix } = opt + // 拼接请求前缀如api + if (urlPrefix) { + axioxConfig.url = `${urlPrefix}${config.url}` + } + return new Promise((resolve, reject) => { + this.axiosInstance + .request >>(axioxConfig) + .then((res) => { + resolve(res) + }) + .catch((err) => { + reject(err) + }) + }) + } +} diff --git a/admin/src/utils/request/cancel.ts b/admin/src/utils/request/cancel.ts new file mode 100644 index 0000000..b092e1d --- /dev/null +++ b/admin/src/utils/request/cancel.ts @@ -0,0 +1,31 @@ +import axios, { type AxiosRequestConfig, type Canceler } from 'axios' + +const cancelerMap = new Map () + +export class AxiosCancel { + private static instance?: AxiosCancel + + static createInstance() { + return this.instance ?? (this.instance = new AxiosCancel()) + } + add(config: AxiosRequestConfig) { + const url = config.url! + this.remove(url) + config.cancelToken = new axios.CancelToken((cancel) => { + if (!cancelerMap.has(url)) { + cancelerMap.set(url, cancel) + } + }) + } + remove(url: string) { + if (cancelerMap.has(url)) { + const cancel = cancelerMap.get(url) + cancel && cancel(url) + cancelerMap.delete(url) + } + } +} + +const axiosCancel = AxiosCancel.createInstance() + +export default axiosCancel diff --git a/admin/src/utils/request/index.ts b/admin/src/utils/request/index.ts new file mode 100644 index 0000000..d278983 --- /dev/null +++ b/admin/src/utils/request/index.ts @@ -0,0 +1,130 @@ +import { merge } from 'lodash' +import configs from '@/config' +import { Axios } from './axios' +import { ContentTypeEnum, RequestCodeEnum, RequestMethodsEnum } from '@/enums/requestEnums' +import type { AxiosHooks } from './type' +import { clearAuthInfo, getToken } from '../auth' +import feedback from '../feedback' +import NProgress from 'nprogress' +import { AxiosError, type AxiosRequestConfig } from 'axios' +import router from '@/router' +import { PageEnum } from '@/enums/pageEnum' + +// 处理axios的钩子函数 +const axiosHooks: AxiosHooks = { + requestInterceptorsHook(config) { + NProgress.start() + const { withToken, isParamsToData } = config.requestOptions + const params = config.params || {} + const headers = config.headers || {} + + // 添加token + if (withToken) { + const token = getToken() + headers.token = token + } + // POST请求下如果无data,则将params视为data + if ( + isParamsToData && + !Reflect.has(config, 'data') && + config.method?.toUpperCase() === RequestMethodsEnum.POST + ) { + config.data = params + config.params = {} + } + config.headers = headers + return config + }, + requestInterceptorsCatchHook(err) { + NProgress.done() + return err + }, + async responseInterceptorsHook(response) { + NProgress.done() + const { isTransformResponse, isReturnDefaultResponse } = response.config.requestOptions + + //返回默认响应,当需要获取响应头及其他数据时可使用 + if (isReturnDefaultResponse) { + return response + } + // 是否需要对数据进行处理 + if (!isTransformResponse) { + return response.data + } + const { code, data, show, msg } = response.data + switch (code) { + case RequestCodeEnum.SUCCESS: + if (show) { + msg && feedback.msgSuccess(msg) + } + return data + + case RequestCodeEnum.PARAMS_TYPE_ERROR: + case RequestCodeEnum.PARAMS_VALID_ERROR: + case RequestCodeEnum.REQUEST_METHOD_ERROR: + case RequestCodeEnum.ASSERT_ARGUMENT_ERROR: + case RequestCodeEnum.ASSERT_MYBATIS_ERROR: + case RequestCodeEnum.LOGIN_ACCOUNT_ERROR: + case RequestCodeEnum.LOGIN_DISABLE_ERROR: + case RequestCodeEnum.NO_PERMISSTION: + case RequestCodeEnum.FAILED: + case RequestCodeEnum.SYSTEM_ERROR: + msg && feedback.msgError(msg) + return Promise.reject(data) + + case RequestCodeEnum.TOKEN_INVALID: + case RequestCodeEnum.TOKEN_EMPTY: + clearAuthInfo() + router.push(PageEnum.LOGIN) + return Promise.reject() + + default: + return data + } + }, + responseInterceptorsCatchHook(error) { + NProgress.done() + if (error.code !== AxiosError.ERR_CANCELED) { + error.message && feedback.msgError(error.message) + } + return Promise.reject(error) + } +} + +const defaultOptions: AxiosRequestConfig = { + timeout: configs.timeout, + // 基础接口地址 + baseURL: configs.baseUrl, + headers: { 'Content-Type': ContentTypeEnum.JSON, version: configs.version }, + + // 处理 axios的钩子函数 + axiosHooks: axiosHooks, + // 每个接口可以单独配置 + requestOptions: { + // 是否将params视为data参数,仅限post请求 + isParamsToData: true, + //是否返回默认的响应 + isReturnDefaultResponse: false, + // 需要对返回数据进行处理 + isTransformResponse: true, + // 接口拼接地址 + urlPrefix: configs.urlPrefix, + // 忽略重复请求 + ignoreCancelToken: false, + // 是否携带token + withToken: true, + // 开启请求超时重新发起请求请求机制 + isOpenRetry: true, + // 重新请求次数 + retryCount: 2 + } +} + +function createAxios(opt?: Partial ) { + return new Axios( + // 深度合并 + merge(defaultOptions, opt || {}) + ) +} +const request = createAxios() +export default request diff --git a/admin/src/utils/request/type.d.ts b/admin/src/utils/request/type.d.ts new file mode 100644 index 0000000..a7f364d --- /dev/null +++ b/admin/src/utils/request/type.d.ts @@ -0,0 +1,38 @@ +import type { AxiosRequestConfig, AxiosResponse } from 'axios' + +import 'axios' +declare module 'axios' { + // 扩展 RouteMeta + interface AxiosRequestConfig { + retryCount?: number + axiosHooks?: AxiosHooks + requestOptions: RequestOptions + } +} + +export interface RequestOptions { + isParamsToData: boolean + isReturnDefaultResponse: boolean + isTransformResponse: boolean + urlPrefix: string + ignoreCancelToken: boolean + withToken: boolean + isOpenRetry: boolean + retryCount: number +} + +export interface AxiosHooks { + requestInterceptorsHook?: (config: AxiosRequestConfig) => AxiosRequestConfig + requestInterceptorsCatchHook?: (error: Error) => void + responseInterceptorsHook?: ( + response: AxiosResponse > + ) => AxiosResponse | RequestData | T + responseInterceptorsCatchHook?: (error: AxiosError) => void +} + +export interface RequestData { + code: number + data: T + msg: string + show: boolean +} diff --git a/admin/src/utils/theme.ts b/admin/src/utils/theme.ts new file mode 100644 index 0000000..d11fe18 --- /dev/null +++ b/admin/src/utils/theme.ts @@ -0,0 +1,74 @@ +import colors from 'css-color-function' +const lightConfig = { + 'dark-2': 'shade(20%)', + 'light-3': 'tint(30%)', + 'light-5': 'tint(50%)', + 'light-7': 'tint(70%)', + 'light-8': 'tint(80%)', + 'light-9': 'tint(90%)' +} + +const darkConfig = { + 'light-3': 'shade(20%)', + 'light-5': 'shade(30%)', + 'light-7': 'shade(50%)', + 'light-8': 'shade(60%)', + 'light-9': 'shade(70%)', + 'dark-2': 'tint(20%)' +} + +const themeId = 'theme-vars' + +/** + * @author Jason + * @description 用于生成elementui主题的行为变量 + * 可选值有primary、success、warning、danger、error、info + */ + +export const generateVars = (color: string, type = 'primary', isDark = false) => { + const colos = { + [`--el-color-${type}`]: color + } + const config: Record = isDark ? darkConfig : lightConfig + for (const key in config) { + colos[`--el-color-${type}-${key}`] = `color(${color} ${config[key]})` + } + return colos +} + +/** + * @author Jason + * @description 用于设置css变量 + * @param key css变量key 如 --color-primary + * @param value css变量值 如 #f40 + * @param dom dom元素 + */ +export const setCssVar = (key: string, value: string, dom = document.documentElement) => { + dom.style.setProperty(key, value) +} + +/** + * @author Jason + * @description 设置主题 + */ +export const setTheme = (options: Record , isDark = false) => { + const varsMap: Record = Object.keys(options).reduce((prev, key) => { + return Object.assign(prev, generateVars(options[key], key, isDark)) + }, {}) + + let theme = Object.keys(varsMap).reduce((prev, key) => { + const color = colors.convert(varsMap[key]) + return `${prev}${key}:${color};` + }, '') + theme = `:root{${theme}}` + let style = document.getElementById(themeId) + if (style) { + style.innerHTML = theme + return + } + style = document.createElement('style') + style.setAttribute('type', 'text/css') + style.setAttribute('id', themeId) + style.innerHTML = theme + document.head.append(style) +} diff --git a/admin/src/utils/util.ts b/admin/src/utils/util.ts new file mode 100644 index 0000000..1f54b8c --- /dev/null +++ b/admin/src/utils/util.ts @@ -0,0 +1,171 @@ +import { isObject } from '@vue/shared' +import { cloneDeep } from 'lodash' + +/** + * @description 添加单位 + * @param {String | Number} value 值 100 + * @param {String} unit 单位 px em rem + */ +export const addUnit = (value: string | number, unit = 'px') => { + return !Object.is(Number(value), NaN) ? `${value}${unit}` : value +} + +/** + * @description 添加单位 + * @param {unknown} value + * @return {Boolean} + */ +export const isEmpty = (value: unknown) => { + return value == null && typeof value == 'undefined' +} + +/** + * @description 树转数组,队列实现广度优先遍历 + * @param {Array} data 数据 + * @param {Object} props `{ children: 'children' }` + */ + +export const treeToArray = (data: any[], props = { children: 'children' }) => { + data = cloneDeep(data) + const { children } = props + const newData = [] + const queue: any[] = [] + data.forEach((child: any) => queue.push(child)) + while (queue.length) { + const item: any = queue.shift() + if (item[children]) { + item[children].forEach((child: any) => queue.push(child)) + delete item[children] + } + newData.push(item) + } + return newData +} + +/** + * @description 数组转 + * @param {Array} data 数据 + * @param {Object} props `{ parent: 'pid', children: 'children' }` + */ + +export const arrayToTree = ( + data: any[], + props = { id: 'id', parentId: 'pid', children: 'children' } +) => { + data = cloneDeep(data) + const { id, parentId, children } = props + const result: any[] = [] + const map = new Map() + data.forEach((item) => { + map.set(item[id], item) + const parent = map.get(item[parentId]) + if (parent) { + parent[children] = parent[children] ?? [] + parent[children].push(item) + } else { + result.push(item) + } + }) + return result +} + +/** + * @description 获取正确的路经 + * @param {String} path 数据 + */ +export function getNormalPath(path: string) { + if (path.length === 0 || !path || path == 'undefined') { + return path + } + const newPath = path.replace('//', '/') + const length = newPath.length + if (newPath[length - 1] === '/') { + return newPath.slice(0, length - 1) + } + return newPath +} + +/** + * @description对象格式化为Query语法 + * @param { Object } params + * @return {string} Query语法 + */ +export function objectToQuery(params: Record ): string { + let query = '' + for (const props of Object.keys(params)) { + const value = params[props] + const part = encodeURIComponent(props) + '=' + if (!isEmpty(value)) { + if (isObject(value)) { + for (const key of Object.keys(value)) { + if (!isEmpty(value[key])) { + const params = props + '[' + key + ']' + const subPart = encodeURIComponent(params) + '=' + query += subPart + encodeURIComponent(value[key]) + '&' + } + } + } else { + query += part + encodeURIComponent(value) + '&' + } + } + } + return query.slice(0, -1) +} + +/** + * @description 时间格式化 + * @param dateTime { number } 时间戳 + * @param fmt { string } 时间格式 + * @return { string } + */ +// yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 +export const timeFormat = (dateTime: number, fmt = 'yyyy-mm-dd') => { + // 如果为null,则格式化当前时间 + if (!dateTime) { + dateTime = Number(new Date()) + } + // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式 + if (dateTime.toString().length == 10) { + dateTime *= 1000 + } + const date = new Date(dateTime) + let ret + const opt: any = { + 'y+': date.getFullYear().toString(), // 年 + 'm+': (date.getMonth() + 1).toString(), // 月 + 'd+': date.getDate().toString(), // 日 + 'h+': date.getHours().toString(), // 时 + 'M+': date.getMinutes().toString(), // 分 + 's+': date.getSeconds().toString() // 秒 + } + for (const k in opt) { + ret = new RegExp('(' + k + ')').exec(fmt) + if (ret) { + fmt = fmt.replace( + ret[1], + ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0') + ) + } + } + return fmt +} + +/** + * @description 获取不重复的id + * @param length { Number } id的长度 + * @return { String } id + */ +export const getNonDuplicateID = (length = 8) => { + let idStr = Date.now().toString(36) + idStr += Math.random().toString(36).substring(3, length) + return idStr +} + +/** + * @description 单词首字母大写 + * @param { String } str + * @return { String } id + */ +export const firstToUpperCase = (str = '') => { + return str.toLowerCase().replace(/( |^)[a-z]/g, ($1) => $1.toUpperCase()) +} diff --git a/admin/src/utils/validate.ts b/admin/src/utils/validate.ts new file mode 100644 index 0000000..0f8d8f1 --- /dev/null +++ b/admin/src/utils/validate.ts @@ -0,0 +1,7 @@ +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path: string) { + return /^(https?:|mailto:|tel:)/.test(path) +} diff --git a/admin/src/views/account/images/login_bg.png b/admin/src/views/account/images/login_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..787837a256587e81c2ffd3abcac0324acff7a718 GIT binary patch literal 59273 zcmYJadpwi>|35x9GjrOQ^P#!Sc`@ZM$C!=j;M4)-utX`!l%kUCa+*0cl|zY02c;UF zR1O $;xH4e;|(#_D1b2!ygP zlOBXXsDgiD*s@6Q=VPz&82F q-Jt~j1(9=a=O?T+3rZ;s+SJqBqqd7B%Ye0ap=omZ6g<$U^ro%+6t6#6^@8aH3F ze0(HDCiycHH&_;hcr&y&Xi|c?vBh83P8qR=@+Nxv7#+z8)gShgnVZYTclNHXOx}@t zW# {wL^CVS7{}f%rMQ=1ip>XMxAaHi14xj6j}4m3~R#r58^!f+hpTf2>4Wu_q76 z(N}mias>5_hs|!>Pql@$x!Eeti2DJv_z!L4?oqeX>t%d)*KFu#9uppP6Q(GAac? zVFt^3^2PTbT&QJ^8IG6u_v#BL8ZXoxWYhW=!c!HLdXKytI&hPH=l({)_mH)S&|OOY zhaprRH>XPAm;SM95ZLPO`$~3>cy$EW5@66~u+|0*QB;|KubHSJ!abz;V*V&+Vbu6q zYGy0h7o|$SZl3N`1BEDk^ACyVe5)9wyKR+S_>0(uiu5=_f-nBEhL>uHg3A1QzgZRP zo(b0XR-avzLfV`hUW_q3cUJv?yudlz5 WGkYUG>sWy6);O@-RU9vXE3N*1MVk^ibQGQ;R #qtK!E9W5UK37I ?xSA zWARF6kNu$84!@Pfp%c`;i$9^JHF$l;BHV&GWO77kn?j1;d*y?Lai#@lx*-CoKk%rr zZvr5AM7YtdpA7%3(l#*)nK-w2?UPNh8Y2D#!0(7~%q?iIIYHM*X_B(pX)oy|S *w*&G8obVg{-Ej=!~p J)dQ3iz*Y`rs=I=}2|9KNoK$Mk*4>BtkmiO*YI9l*mF z-rqRAV`M)mT-jjh&om22CeT0E1RyuE5=u$_+jzuNknLo9=^9?&trZH2Qqa%9Y%aR` z$C811tPnr_cXQ}=lute=!+O~qYr-v=y9MU>c;ybLDV=d;=p3d{3ozjtO2r2LX*hL8 z8`R;+3zNLsQ=xT+7VA?PwNKTkn)o@{ghNX88-F`L{ &2^;C@lv1ns z>@-)*Y0VScN3++t1=UNdXDTHZR;);)3+&auH(eS*ELw}#U;GYKr&P#cu eo!nLr@88Q1@~{W>K;^nxf`V&GK$=>xp|qL4oO;5-W~qf6?HpH6!J ?(vK r+$wHY6Vh+cIjt6h6oi@$D}7QcUm_;O%M?J27Y z)7b(gysH^yfs+ eK`ni+h lc=v*maD- r{W^k@xPb<<#8L;3Si+k+z@1-I z=1ev5VLK+d;zh} aKMM)ikE+7DSibm3~@vYUdfk9CHJlrMYnSL@#3YVtz;-(MdKv dY5H%RDX8%_vXGx+JK$+=YV0TzWO@?z-IUP^5A_wcj;xBF>Yt-VR(- z)-S<$3v^j~P{yzQf2!ZVj2``%K=NI(;klW&s79T?GG3hTM ta^mdS}vcwW_4};5)?tU9_&MxfYR#QcItOn1|j~B $lBFy>E#9K3- zr-xvisrSZ1A X* zmMuJhP{fRW*dFj70JFFKHzsDvh&JT3_@;}mb4+} NJ>LyBBj`bra6G=?(K2!GnFzJ4k=3f^L45 zsBIFCpQ;Cdjc_IoYw;JAm XS-^z5Z0qYbJ4u$Gp9szNC>8dBDcCYG)f&mR+lf+C`DWjX1E6 z-tlW)6uW14LI&;xH#!QrB@zEyM$SnIGrER0_ERBU?1#(?Xj<{%Q6sf4muXr_d*%9C z#Id-)2~#bQ6(x60RS5@rPoOn%O{WuKZTkGV@eaAqg4gW~;jjflAhGk`=8p@9bI{p~@f`>&^a}x?S~W zrJ)KYVjXc-dprZ+yaAWGIiVyg*N?s=QNHzn5>FhU%+3|^EpZ>Pn0ej;ZQ~72Ld@H@ zKXL=h{*^o=m$|p|VbXnmCN0P9@x3 |Q!G=G z5WB1b6w5)7&id17zS0@$KfR8=q=7SWxvzSgn24)p&&ik`vnNvT$%|g1HI2*ekCOB7 zAfa&;G8EJ^fbimo3*ITs8@jzjk6Ju4G5X%O*^}b&k5~dVemitX=EvW2<~Ved2DSsS z2<50eGPkl3jd~$t=(rExb>AR_UVDo(TvUkD-|9QPT@G&hEy?J8!AT%pj>CNj!MX03 z`B!TfJYS`rR>>wEIvx{OEx>#}Bc8Gss9_&JjT{{%E8;-=M orgx9#8mU>Zl_=Obz{0EO3WfqvCClf-O9vgdFwVdeG z84bR5@q=a!{z})ZOAL}mX|9t^tFf!C^M>#yb7I+w`RnrjmpRG9sSzBpl_si4=Wib9 zfA(UiF8)TRXZoHiAHJ->Rz@JLswEdOSzv))i?sIo!BBB$wHINp2@2CX0<#vb%KNu* ze%hc=uc}@2W+!%CIrUE~yy;O9jVs8+rp~6&J=b2cO$e1|hoi zf_nJe8^<4ImpMJ~Qo=Q#_mE_WWquQr^g=u&<@T`5fBm|SKCbMtlAkVCfB2}eoj<2$ zfKt_+{eJPva`UU+5^i4})AW<@BDeEXNjR^04(F9FF4twXc3Ao(;YJczG9pDe*k+`G zn9}@?A=QyR#?Y6NXB(m}Wzv*zVY^5sgfk#8Nm^<6vBwt!sNOSB|6Hk*6(d}x+OtIF z-!Tm%0;cCzm!ZpF^ytfDkCO)w8w%~#|5wfK<@9Z4WzAl=z1p-fo5CZn{2QJmu<%%m z2FLC~oqv#p C*A^M9>yM6G>F*QZ_UVenM8|D{7PKCP?8B1&e z@g3>lb|i@1fh~`!>dU`gVq8D~hN6)Rn*6~R`# NZE(i=P6;y z`}MDgPAD;udcAMZGzT)69$-M1JlPlo?29g2TPV-QxyAw|4x&vC$rzeR*GZ%Mu4UIS z&NL^c)Oko6=Sj@!rLkh3@$m7OdwKt`_{3wz1-DYm_#Tpdi&Kv2=%ZAV#RtAR8|7+X z?{UGB2F`w{|954@aAF7F51~ILd-3`4m ;wkCQ(qsnRT>9&1C^e|Coqr@CWEDIAFKz53 zC(D?BAS$W;KVz-jtLq;$xLi@S6UxFj^d-m-rg{$SV-f2QbxJFZyC`LhD~|suFpIBS zEup$&&b$Jn!E*l6cyYMH4PuJaj&ZWMm0SD%XQh(hp8kW2AsEMQ2jY;bz*8dN=;4|x z8th>o?mu}n }ovIAsskt~Axd22$7-v)AQg;><>M&zGfZ%LI4 z`&No)F%X)C-QHUKGXuhdT;`QM=^(1VU86en4*0CVSyOXVFt%Vr#)aau=X%puD9%UQ z&lq&t>Um|?XSZfHTu7{>hhY=xn7#6zw=BQRJDy02>bW`rJt`kdYufqod&-|*F9VAv z(bVGyxw*rktZDvpzFTVqYC&FJmH&RX{HOxIFdwpny&!zEq4>k5 (}G-f+~L`@;4D-|a+pz^ld%@fPZ=P*}=v2c)6r>)W@^N5m1Zb59fXid>=`{M6R z5RRENoFw@@MnM7tut@E_nYY;mg$#_t%cTuu?}zdmlR1A%?-^{W{N N7O0LPz)?0Zg zq@8X4U@g!REj$8o>n^4!@%6`Di!)bff|z4`7sJV#Aq>fy44MtR2&WI(@8m4w;Of}v zZ#Tv=BpMl*>z-rlQEmQvhr`9+Wp)^pi-_OXOo)ww4rprU{0jy)Gbg-u&fO}LEVknr zpGv`p@jG&u-+x)ysQ^zHSyTMT?qzo%cg#B#{X1@_T)#mnkz!m|d=9f9Osqwr@4SD} z2eN>l8Cq=86rJ>bA9)^=_TxNA$sR+i{XN1-1USdUt5WWnc^9NbtwB#h+GN4Jzls_B zY9BsKE*n?ppqNjq)en>{Un-ZCafzAYH%BqKg)}>%_)}|Q(`Wv9Ar2aN1E8tZ?_#h$ zNHBwRB-vuIi(-A!nctt~pX{sLXDACYPvmUm9x;9pP~ijNMR7$*3^FW|jSHae_N>+t zX`ICH-|v*qHVP_?r%Piwfmz45Gt(5YIBQXQ6B+F}s4Qv4wV~JCO@^qI(CkVQD7W FGKtXcUxyu>E#ocAD$a`gG`T+DJuM(nf|B^MbV*!DGW*o z%M9K>A2WQOlF76WmEL;)BIZm==wL4Ez*@7}jz1KW_2RS|XsF`1fBrhyh<^T_?7_Pr zKBkd5i7L!GdFTcvJ6T-2^t Gp?UM%3T7IOQ} z2V^p@g|G;a27d$Ibqpse`gZbAFe*cC9p2U6+I+^QdsLOdU6Ti%I`}-yX^dfP53aWU zq+I2hC*Ak+#Fm2pyS>0i_ge}{8S66!ZI)ZiF80m^*o|TM+Y;ryje_u93U^mvi;|)v zZu;kMJ}CR#BqLc|+84hVz8!)3Y@p1atisjt6_vbdp&*>d3Urpe`Bk#_ CJ !o+F)!T8jj?->$zp;56#rnD6LARE|c{UxSr zF*>V*GHY;H)=k3r+PRkmUJ%Hr@bjh1nt>^WMXWB(U?rRG>wboAdls@Ix{8hvnuJd# z_PodyZ;3#?lJn=T$&jm@)3IR#hA{9Czcqf?@Ut;~B{qzuM~K)K@b3Tj`=3GauN2p? zK(6@gO^nK=)j<;3vs#w0|D&n&ANSs_k^xL bQzzZ%WTToLKhW-*a#C#DB&V`0unp3(g{LpOhSX zA2ZDQZvO^l!#+6pS!dInnfK=m6MD^pg&mgvK$Y!K6~GmjxCwV!mY7_?Sci&9ZlB70 z%!>S4Fs;Z+Tv;O%v?cnls_ zKc$DS=*l^w-Yk5QcH8YcL(&4u@ (1m^-0 bMUeX2HMS7O&!xDD($>SvzR J ~vP;W^`)d_xwWgzB+du(UM&UsyBMLptMQ6yN z;uYVScjw2xLMf^zaa+$A1ufOm&Sc_-g(Nt#MXfK6QYS5$UBPC4V6)G2B$3Vw1Bpp{ zk@&U9r^Xz9RDhXRXY<6xa)T#(XHQC0nrNgqRWtyNu^x^8 M^m}Af~4E+wxSgM$txUUt~`KU(0RH!we%($6u7jwB5@;vRGobRnG?g zoNh&jT9IJEYHG`|-Xe3?Vz9A#-nW3eHHUaC^(p_pJ8fh|mLfkzOFNsXJ**}&)+~Ce z+pO_yX(zBE_-p@-Au+@cEAx+~00CC>-qD9lZK;(oK`d)fm(5jazYM6y9zEZe_Y=|< zYC?V1KuIZ3`_IPu|8oSgo*l{)tG%O M84XL&5d1}^=!kV 5aD)*h(tv`mltnam^B61xLBjKJ N83E5_2SkVJT-|d28*DNhH z!J7L<1$h^*HSO4Rzi}XHQ_P}kp+8LG4{(}@nFr)O6HF2}tDo}i`J`hv{&}Z6u8K8# zA~o%bQi_q n ad(3#ZajTc?rANs?W=X0PS+O3$!GHk{v zd<%{>rGIr3R{!aICp#7sx<6fBwX3lZr%L-PTQoZQQD;|OmA0D AFq#nUts25 6zl#r<*AK;CvHRP zLDzP2+`LHhp`kSbI$mV3^T;8nDUGu|pseX+07G&UOgukca>{XC3y90@y*zOo3Ll@* zei4&aG7;D&e1RO9I^IPJ?m;htt|fAA;_C3Snxe_slGE&`)&dj1O%nn5RPR|_T|qL- zf&R4(DerkTqJt}zxw)kQp-OvVK=sSHn^*_UR l2G{-U6 zUIljhrs ?FR@px!zd>U=bJk8lE=#T&)5B#8+eS0R|-Knk_CUN^p9dH&tu$; zFPH!GXi8T*>4jI)Ao!u_Mm3lnw~$*}uKvFGI(uL3E>RBiVl#57Q8B_G>ZcwPr5?!i z-$mV~HPp8vzv0%YCy$SQ6Xr0tHzV8BX{0aVvFBaYxR}v>weJ{MpAOmECn Z+Q^k8}}{C;lyWi7v4H-S|NVz~^8s(1yAejxNEZ!|hXEHMl_3 zGiyP|KCs0 8(cH%_?X5Mepp{9+TWFRrBf_=5$gxSoCVd57nxg^gd zH9`#+jN{~(Ofxu6E5!`X0WM@t8mWyKfFS6YP?WzJU8LoTAGs<#^)Q#UbkIM49%V A zwSFK0#af+Qv0P7g<7y^;q)F&to;v*x50)3HHnKYFU%$Nh7KJo|6aL~vq8s}8Z{&I+ zTMG(6Q=>k;{kEWLO5gTc77%bzXRcV888CY&EPz?mM`usSZrNJlR8$KMar7ahmmIBB zDR?1xr7yXCtv7>n#rGm*uVw_a2@3;V^}$1r+?-dfyw5)-g7zW8D%cC9F^-FZ$j7u& zuIein*aFc}1#4zECu;$=sgQG+^YTq0i-~mhJww*9;?s0>QM>GxUoR_fHYMAx!&kf& z8maY2Q#9-3u?|7A-c{s2>_yC_NS06W%MW`l09{}g>O*5AQd!V)j=7#a+j&ky +6cz^!nLRk0xwdT+-4dnfvX|PQ=_30z~?v+=Oe|6g7Mevki8_kh3^MN zS^w){$4-h(jocxO%A2KvjNWG9)QQ)UPXP>poC}>63b_9@n_84<1iSd5igL}lpUws_ zft{6)t#ew*abS!M&;;!>XT-vPfN48j(NZ^a{XEtLU2wA(nXeCV$bjI#yp- QI zMFe~q07Kk@87z%47&213f4Mh|JE{5wqSEl9Xp7*-yUa6P1j^Xk4Yo)(;a&h0hb;M6 ztiW$J8Z;i`id(-i@0=oFH*#LRu_^UKMV`ZCtMENO2}iU*5V82w-Hc1-BnqBPX75mC z>+9vYEC2N|>Yp{2^5u$A@v=@_ND(R!b^HWo{;T%%#^oD1Pa>M_bP4lD^uHG-{-4(7 zq%$O0Etk1P`_04ZA!zF z^PZi=QR%WF;z(MpyHtG?xfq9KktN`>>S?*_42k3tPpt0>7O*U&chinNI(5h1BOq2) zvd&l|1PTe0J{rFHI_jY+smf%krYZBEHwlNmG5l!E3*(Q#j+*GJ0kylJm7He?{rwYJ zeFrE9l^4}|S30W{$+H=PRC&Msjs=Wmwmf~XLasIZzEN>%dSYbB%l)-A>1v{JS+jCw z>{?Vt4s&5%rMX-YXv-I cIxSs^<2%G( zy0378&hCsNgwnr`2-nfE22k VIas-WtHY+)M_wZo*A9=`;iMGdI+(iQr3KDSF_R#!CX zg(zm_;<0ilWF9jjnUxJ*%Z0~cJD}&Oo(R!;@NKCnrEH>eScpbV%X|tCz7lFJqrrEz zqXg5(bLNq_l~;*0Sy2yqF!xDFS9fDCRQwpdhs&Jn9YuMrDWMQkm~TSKCq3DY5);h2 zv$Aj?gQTkb0mpx?D?X$tDmB4i42yj^C|Jj#fA+MD=xj3Gyc5_@Q+%Y(Kh~zW-9xhZ z3LPQ(-D& ~DFel@-eIOPCMe$`9P;ub@anYWAV)I@3u$?}$FM zy`g*V@gXAaVJyp-V7ktnfXlape!bw71NdYjre*F@ZMS(!MrtnVo93e(+9m~WPi$U# z^77-cEB^HNQOE@Xf3pg{Zp_~z1Hm+}s8Y{j5{F(2!RL%4&E!0?)Y3)n8sIGA9S) TULHyr+`&c8Jzw8rNys>z@kVKIaHzGr!CY5L&(X?bZ3!?c zpZh2*h5AQ~pqpOyMC}gDATh<=80$Wk&hkSREk(iuGjzZF8lIGl`=ypTXk+Ls EGjx^cP8^{VPq_J#O|Tg8irO=gauUbP3~SPM|G zvM57r<${}goyb%3L+VuvF56)$LB&r(#b?oPK*a-;G1)lmVoW7IP-D`3%#K}b=3776 zX%5I<&AADwEu-7hjM@>A*sP5TNZ3iaLI+2-=?2}B)|v$v^HEuHcxR{#o8%_^(HV;$ zPiGCPr0$o8{pC;@Jm$iXaFkK|+_7N~t$IOb6r1H7Gvh3$@DGsOfLX1sG%fxmU1_x` zUVxa}q@CBd7-M<@_l9P4fpc$oC|%sO#)EQ>J7c4qPlSehHx{m;DP-kQX3OBrI~<$F z3nj)1m>oo@?gaPg9fLCP!NyB|^^r&%H7H@{lB-qKr8Cy@3KuwsucW<##jEjHr@hyF zq}9G+4OZF{Q*yyenvquWaX528xZXT Ze4abG zQ69i_@sm(=7(3qiZnu2{adz}9A_f Dm8S^F2_5V z%ba@`8Vp$`X5OIn7P$yC_?muz4$c@j@4K2T>gNpNu$)K(i`neJE~!#}+BF$%Nz@La zZyObAgQ5>`#Mn+u>&_G4L#!+us$5| 95u#u+#`;}g zAwB|WHj%ch4@oxj_LTNjJcG0|k2!}MP{`6~?`|yBucO<-<-pDcoU4X0aP(EO*iNb? z+dL#uNE!I@w2Y}a?qM8D5#A&tT8ZB}57^m-ZVf#`mXdg1HBJ_l;M5P%WkdkPyI;21 zsTFq4q1>E)h11_psyx&H7#?%As4)SpRY~^npj_rWd2yky+<-O4EOb5nZ5gB1&w;W5 zID97vXu6BT?nk5ibImcY&f>-#*bWX=7&CzO8E(>yL75Gx?1Bu&fo4=bf|UAe?Inij z;OhON(?! rWQvER5FC_|h4(7QdkWMFQC5(_0r@u|^#C_aVSoo^ zYE2vt_i%)BEnukVsO|%VOXEMW*nr>q2oGXjUznK~^)y{Poo%e)iN$Y5wmr02CkrPs zY9~GkKjtwVW+KzYF3H@7x=ONA{HA3JH=W+4^7XsZ<{!Dt@Da`nO@2K0eHIO49P-ga zVg`K0i<5Adk7!_DdsAw-Nh1bL2k>3lVMJ{)Z7f%Z1z`t?#Ij+)Rej}$DmHDC=>)#6 z10|mu?X@81(@z`APb$&&_gbnqOIye-E{)m9s6FK%SV7-*FdcbP#(_9nK?8?2wu|jr zoC8cD0>on~>uGz_8;y2ivW^}stW8T7o3yOLw{I7drmbW|0;u1%Eeq^ 6NfdgqJMeafh%_ueWG%iFZ_E?#7nPIC&f%0 zF(kZ|y#I3=7PkW#XPlB`YSTTK{evro?F1C`2dUpb?t67k76E@pB-jRdChSZX=mA$8 z=MzeIFlYk*m>s1U;BrmB&SQ+Ji}Lq93sc} $@l>c>vNUkA<(WhFu`F`{K35W1vUZz1oHxD;i`#=VE7t`3E>Jh07o5Eyj?Xl8 z2IFdIl#*nGz{a`t$h7YeliCdKCHi|%YPm_B=(IOD{;?c={V6?+&-<6 5mH3uFS-UmWzgT <~7{tVIc)l%jp4PgG9v@+L^s! zmM=UV;(QBJ4mDRIYWvZ*z1tAW;ZA-qo|zX|TM=;0+I_ql|EvWrm~Un+(^&!dx(0Ns z4UVt>QJ5%~7`m>`v*?m__vU%y1lD50j)DMf56VgI(;=1SUtaY0tB&i4HST;da*B`* zD_PrOXp3=oT|DPA^52m2GU(f0ysRlSu!BXx=X!0I5vdxIf88*7_4VQYPUZT;o9mV+ z{EKO`ye5~~gD$Hvw^*T#bA@(kDzb2s%5&
F#~nxjA*x#R8QGD`EUpUDEnFi?7t00aBj7vA@ln*ECUPF2xd7>+ z!KWsvpzg(hLpDGHG-NTCt|8A3hZH4H;4k!AQ(fw-CUriQz9ma4AHfahmd|B~b(J3y zQ}nI7fru=Oa$XoiiSJx7^VveaHtP5V=RU0kZ}tgv CR*s2<;bxQ^-5&3PQU*96EghAkkeHO~%>vClxN{{~=sT``RRT~cC z!s6sx_shWvg$P&6Lltz=_v74|MGovw;Hb$V1Zt73hX9Y={a`Zujr}!UkO>u&wJ@u5 zy2l1T)fUgX2i&2E+T*!>>KM}+^B?oHjTZqT>PYzP6$E@RI@1X1($ii|AMf2;xTZuy zh*-d3gMDW#zUl2nerx;n{cu3aQYA_Wcjo7f-j8aaL=HJ$)i}oA&t%{aW{>vfw0qU- zrR?wfycE-_;{n!9-r~G+*5p%32$9~A?nW}fTmCb#X&o}IT2dey8fzpU<4*38Qg@Y- zBu!Zk_P4137`+nSx!_ob_0J#AWsalKfC{8BrVkF+432c=l;;dNuq#jB9Al0(epnlE zlFK~!QMg#0pXtKBdWl%6-E#VjA`)J7VDIE!&_oZ*Yd0B6)uD_#*~Iybp_6_h^^p-J zZtf3VJ8yB!Zs!`ZG%B?j>im&ULYpFR+DPSuoG9ln`rk#mq3!hm+2AL48<1N=)}7t<(0 zh@NSq7QKrbGa6KGJ~8v>1q(It|1zViRSRfM SRP?c1e6>F4J0%`YOy@8aJP54hAT*!mGSR;n9YxZ*ac( z-qI^V`oQY^Fb7HZrVn9&A;f7U0aG|=^_UnkDy+DD`wK&x<%;i$LALGippWBDXkmY9 z(>6W^M->&bBJx>(&^>0`&ONmYE4ib_Py3^JD+^@49t9T!FineG!D%&g+G$V05@Lw5 z^D zhDA+xH%1S#7D-E_=qB8HK1tJ`=O@2{UTjaf#O*ZlWO)c~$%-OVrIGezPx;AJ