├── .npmrc ├── src ├── views │ ├── 404 │ │ └── index.vue │ ├── articles │ │ ├── index.vue │ │ └── list │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── index.vue │ │ │ │ └── script.js │ │ │ ├── script.js │ │ │ └── index.vue │ ├── researches │ │ ├── index.vue │ │ └── list │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── index.vue │ │ │ │ └── script.js │ │ │ ├── script.js │ │ │ └── index.vue │ ├── team-members │ │ ├── index.vue │ │ └── list │ │ │ ├── script.js │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── script.js │ │ │ │ └── index.vue │ │ │ └── index.vue │ ├── home │ │ └── index.vue │ ├── login │ │ ├── style.scss │ │ ├── components │ │ │ └── form │ │ │ │ ├── style.scss │ │ │ │ ├── script.js │ │ │ │ └── index.vue │ │ ├── script.js │ │ └── index.vue │ ├── orders │ │ └── list │ │ │ ├── components │ │ │ └── detail │ │ │ │ ├── style.scss │ │ │ │ ├── script.js │ │ │ │ └── index.vue │ │ │ ├── script.js │ │ │ └── index.vue │ ├── users │ │ └── list │ │ │ ├── script.js │ │ │ └── index.vue │ ├── contents │ │ ├── index.vue │ │ └── script.js │ ├── ads │ │ └── list │ │ │ ├── script.js │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── index.vue │ │ │ │ └── script.js │ │ │ └── index.vue │ ├── jobs │ │ └── list │ │ │ ├── script.js │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── script.js │ │ │ │ └── index.vue │ │ │ └── index.vue │ ├── categories │ │ └── list │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── index.vue │ │ │ │ └── script.js │ │ │ ├── script.js │ │ │ └── index.vue │ ├── app-upgrades │ │ └── list │ │ │ ├── script.js │ │ │ ├── components │ │ │ └── form │ │ │ │ ├── script.js │ │ │ │ └── index.vue │ │ │ └── index.vue │ └── products │ │ └── list │ │ ├── script.js │ │ ├── components │ │ └── form │ │ │ ├── script.js │ │ │ └── index.vue │ │ └── index.vue ├── static │ └── logo.png ├── assets │ └── styles │ │ ├── global │ │ ├── classes │ │ │ └── index.scss │ │ ├── components │ │ │ ├── index.scss │ │ │ ├── iconfont.scss │ │ │ └── iconfont-20230706.scss │ │ ├── utils │ │ │ └── base.scss │ │ ├── reset │ │ │ └── index.scss │ │ └── index.scss │ │ ├── utils │ │ ├── index.scss │ │ └── variables.scss │ │ └── element-plus │ │ ├── variables.scss │ │ └── index.scss ├── router │ ├── routes │ │ ├── private │ │ │ ├── home.js │ │ │ ├── contents.js │ │ │ ├── ads.js │ │ │ ├── articles.js │ │ │ ├── jobs.js │ │ │ ├── users.js │ │ │ ├── orders.js │ │ │ ├── researches.js │ │ │ ├── products.js │ │ │ ├── team-members.js │ │ │ ├── categories.js │ │ │ ├── app-upgrades.js │ │ │ └── index.js │ │ └── public │ │ │ ├── index.js │ │ │ ├── login.js │ │ │ └── logout.js │ └── index.js ├── apis │ └── admin │ │ ├── ads.js │ │ ├── jobs.js │ │ ├── files.js │ │ ├── orders.js │ │ ├── users.js │ │ ├── articles.js │ │ ├── contents.js │ │ ├── products.js │ │ ├── categories.js │ │ ├── researches.js │ │ ├── ali-cloud-oss.js │ │ ├── app-upgrades.js │ │ ├── team-members.js │ │ └── tencent-cloud-cos.js ├── store │ ├── modules │ │ └── index.js │ ├── index.js │ ├── enums.js │ └── user.js ├── composables │ ├── use-helpers.js │ └── use-consts.js ├── utils │ ├── config.js │ └── use-components.js ├── components │ └── layout │ │ ├── index.vue │ │ ├── script.js │ │ └── utils │ │ └── get-menus.js ├── App.vue └── main.js ├── .gitignore ├── npm ├── iview.zip └── element-plus-admin │ ├── utils │ ├── polyfills.js │ └── create-api.js │ ├── assets │ └── styles │ │ ├── global │ │ ├── reset │ │ │ └── root.scss │ │ ├── components │ │ │ ├── container.scss │ │ │ └── scrollbar.scss │ │ └── utils │ │ │ └── base.scss │ │ ├── element-plus │ │ ├── reset.scss │ │ └── components.scss │ │ └── classes │ │ └── base-colors.scss │ ├── components │ ├── layout │ │ ├── header │ │ │ ├── script.js │ │ │ ├── index.vue │ │ │ └── style.scss │ │ ├── main │ │ │ ├── style.scss │ │ │ ├── index.vue │ │ │ └── script.js │ │ └── sidebar │ │ │ ├── style.scss │ │ │ ├── index.vue │ │ │ └── script.js │ ├── login │ │ ├── components │ │ │ ├── footer │ │ │ │ ├── index.vue │ │ │ │ └── style.scss │ │ │ ├── main │ │ │ │ ├── script.js │ │ │ │ ├── images │ │ │ │ │ ├── bg.png │ │ │ │ │ └── icon.png │ │ │ │ ├── index.vue │ │ │ │ └── style.scss │ │ │ └── header │ │ │ │ ├── index.vue │ │ │ │ └── style.scss │ │ ├── style.scss │ │ └── index.vue │ ├── order-input │ │ ├── index.vue │ │ └── script.js │ ├── upload │ │ ├── style.scss │ │ ├── files │ │ │ ├── index.vue │ │ │ ├── style.scss │ │ │ └── script.js │ │ ├── index.vue │ │ ├── script.js │ │ └── composables │ │ │ └── use-cos.js │ ├── list │ │ ├── components │ │ │ └── image │ │ │ │ ├── script.js │ │ │ │ ├── style.scss │ │ │ │ └── index.vue │ │ ├── composables │ │ │ ├── use-helpers.js │ │ │ └── use-list.js │ │ ├── style.scss │ │ ├── index.vue │ │ └── script.js │ ├── file-viewer │ │ ├── components │ │ │ ├── office-viewer │ │ │ │ ├── style.scss │ │ │ │ ├── index.vue │ │ │ │ └── script.js │ │ │ └── video-viewer │ │ │ │ ├── style.scss │ │ │ │ ├── index.vue │ │ │ │ └── script.js │ │ ├── index.vue │ │ └── script.js │ ├── items │ │ ├── index.vue │ │ └── script.js │ ├── enum-select │ │ ├── index.vue │ │ └── script.js │ ├── editor │ │ ├── index.vue │ │ ├── composables │ │ │ └── use-upload.js │ │ └── script.js │ ├── file-list │ │ ├── index.vue │ │ └── script.js │ └── resource-select │ │ ├── index.vue │ │ └── script.js │ ├── enums │ └── upload-to.js │ ├── apis │ └── public │ │ ├── enums.js │ │ ├── files.js │ │ └── managers.js │ ├── composables │ ├── use-xlsx.js │ ├── use-enums.js │ ├── use-auth.js │ └── use-form-dialog.js │ ├── store │ └── modules │ │ ├── enums.js │ │ ├── items.js │ │ └── auth.js │ └── package.json ├── babel.config.js ├── public ├── favicon.ico ├── static │ └── logo.png └── index.html ├── .env.test ├── .env.development ├── .env.production ├── alias.config.js ├── vue.config.js ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | -------------------------------------------------------------------------------- /src/views/articles/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/views/researches/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | /.idea 7 | -------------------------------------------------------------------------------- /npm/iview.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaotoday/element-plus/HEAD/npm/iview.zip -------------------------------------------------------------------------------- /src/views/team-members/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaotoday/element-plus/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaotoday/element-plus/HEAD/src/static/logo.png -------------------------------------------------------------------------------- /public/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaotoday/element-plus/HEAD/public/static/logo.png -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=https://my-app-api.test.liruan.cn 2 | VUE_APP_STATIC_URL=https://my-app.test.lrcdn.cn -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=http://localhost:11010 2 | VUE_APP_STATIC_URL=http://localhost:11010/public/files 3 | -------------------------------------------------------------------------------- /npm/element-plus-admin/utils/polyfills.js: -------------------------------------------------------------------------------- 1 | import "core-js/es/promise"; 2 | import "core-js/es/array/includes"; 3 | -------------------------------------------------------------------------------- /src/assets/styles/global/classes/index.scss: -------------------------------------------------------------------------------- 1 | @import "node_modules/element-plus-admin/assets/styles/classes/base-colors"; 2 | -------------------------------------------------------------------------------- /src/views/404/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/styles/utils/index.scss: -------------------------------------------------------------------------------- 1 | @import "~sass-utils"; 2 | @import "~sass-utils/pc/utils/variables"; 3 | @import "./variables"; 4 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=https://xmxhsh-mall-api.liruan.cn 2 | VUE_APP_STATIC_URL=https://xmxhsh-mall-api.liruan.cn/public/files 3 | -------------------------------------------------------------------------------- /src/views/login/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @include v(login) { 4 | @import "./components/form/style"; 5 | } 6 | -------------------------------------------------------------------------------- /src/router/routes/private/home.js: -------------------------------------------------------------------------------- 1 | export const homeRoute = { 2 | path: "/", 3 | component: () => import("@/views/home/index.vue"), 4 | }; 5 | -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/global/reset/root.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | @include size(100%, 100%); 5 | 6 | overflow: hidden; 7 | } 8 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/header/script.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | userName: String, 4 | }, 5 | emits: ["logout"], 6 | }; 7 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /npm/element-plus-admin/enums/upload-to.js: -------------------------------------------------------------------------------- 1 | export const UploadTo = { 2 | Server: "Server", 3 | AliCloudOss: "AliCloudOss", 4 | TencentCloudOss: "TencentCloudOss", 5 | }; 6 | -------------------------------------------------------------------------------- /src/router/routes/private/contents.js: -------------------------------------------------------------------------------- 1 | export const contentRoute = { 2 | path: ":menu/contents-:path", 3 | component: () => import("@/views/contents/index.vue"), 4 | }; 5 | -------------------------------------------------------------------------------- /src/router/routes/public/index.js: -------------------------------------------------------------------------------- 1 | import { loginRoute } from "./login"; 2 | import { logoutRoute } from "./logout"; 3 | 4 | export const publicRoutes = [loginRoute, logoutRoute]; 5 | -------------------------------------------------------------------------------- /npm/element-plus-admin/apis/public/enums.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "../../utils/create-api"; 2 | 3 | export const publicEnumsApi = createApi({ 4 | url: "/public/dicts", 5 | }); 6 | -------------------------------------------------------------------------------- /npm/element-plus-admin/apis/public/files.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "../../utils/create-api"; 2 | 3 | export const publicFilesApi = createApi({ 4 | url: "/public/files", 5 | }); 6 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/main/script.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | tip: { 4 | type: String, 5 | default: "", 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /npm/element-plus-admin/apis/public/managers.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "../../utils/create-api"; 2 | 3 | export const publicManagersApi = createApi({ 4 | url: "/public/managers", 5 | }); 6 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/main/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaotoday/element-plus/HEAD/npm/element-plus-admin/components/login/components/main/images/bg.png -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/main/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaotoday/element-plus/HEAD/npm/element-plus-admin/components/login/components/main/images/icon.png -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/global/components/container.scss: -------------------------------------------------------------------------------- 1 | @include c(container) { 2 | @include clearfix; 3 | @include block--center; 4 | 5 | box-sizing: border-box; 6 | position: relative; 7 | width: 1240px; 8 | } 9 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/footer/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @include b(footer) { 4 | @include position--fixed(null, null, 0, 0); 5 | @include text--middle(80px); 6 | 7 | width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @include c(login) { 4 | @import "./components/header/style"; 5 | @import "./components/main/style"; 6 | @import "./components/footer/style"; 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/styles/element-plus/variables.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @forward "node_modules/element-plus/theme-chalk/src/common/var.scss" with ( 4 | $colors: ( 5 | "primary": ( 6 | "base": #0080ff, 7 | ), 8 | ) 9 | ); 10 | -------------------------------------------------------------------------------- /src/assets/styles/global/components/index.scss: -------------------------------------------------------------------------------- 1 | @import "node_modules/element-plus-admin/assets/styles/global/components/container"; 2 | @import "node_modules/element-plus-admin/assets/styles/global/components/scrollbar"; 3 | 4 | @import "./iconfont"; 5 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/apis/admin/ads.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const adsApi = createApi({ 5 | url: "/admin/ads", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/jobs.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const jobsApi = createApi({ 5 | url: "/admin/jobs", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/store/modules/index.js: -------------------------------------------------------------------------------- 1 | import { auth } from "element-plus-admin/store/modules/auth"; 2 | import { enums } from "element-plus-admin/store/modules/enums"; 3 | import { items } from "element-plus-admin/store/modules/items"; 4 | 5 | export default { auth, enums, items }; 6 | -------------------------------------------------------------------------------- /src/apis/admin/files.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const filesApi = createApi({ 5 | url: "/admin/files", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/orders.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const ordersApi = createApi({ 5 | url: "/admin/orders", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/users.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const usersApi = createApi({ 5 | url: "/admin/users", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/assets/styles/global/utils/base.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | .u-shadow { 6 | box-shadow: 0 0 6px 2px rgba(168, 168, 171, 0.16); 7 | 8 | &:hover { 9 | box-shadow: 0 0 6px 2px rgba(168, 168, 171, 0.26); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/apis/admin/articles.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const articlesApi = createApi({ 5 | url: "/admin/articles", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/contents.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const contentsApi = createApi({ 5 | url: "/admin/contents", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/products.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const productsApi = createApi({ 5 | url: "/admin/products", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/header/index.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/apis/admin/categories.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const categoriesApi = createApi({ 5 | url: "/admin/categories", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/researches.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const researchesApi = createApi({ 5 | url: "/admin/researches", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/assets/styles/element-plus/index.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "node_modules/element-plus/theme-chalk/src"; 4 | 5 | @import "node_modules/element-plus-admin/assets/styles/element-plus/components"; 6 | @import "node_modules/element-plus-admin/assets/styles/element-plus/reset"; 7 | -------------------------------------------------------------------------------- /src/apis/admin/ali-cloud-oss.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const aliCloudOssApi = createApi({ 5 | url: "/admin/aliCloudOss", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/app-upgrades.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const appUpgradesApi = createApi({ 5 | url: "/admin/appUpgrades", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /src/apis/admin/team-members.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const teamMembersApi = createApi({ 5 | url: "/admin/teamMembers", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/global/utils/base.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | .u-shadow { 6 | box-shadow: 0 0 6px 2px rgba(168, 168, 171, 0.16); 7 | 8 | &:hover { 9 | box-shadow: 0 0 6px 2px rgba(168, 168, 171, 0.26); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/apis/admin/tencent-cloud-cos.js: -------------------------------------------------------------------------------- 1 | import { createApi } from "element-plus-admin/utils/create-api"; 2 | import { useAuth } from "element-plus-admin/composables/use-auth"; 3 | 4 | export const tencentCloudCosApi = createApi({ 5 | url: "/admin/tencentCloudCos", 6 | headers: useAuth().getHeaders, 7 | }); 8 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/order-input/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/styles/global/reset/index.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "node_modules/sass-utils/pc/global/reset/base"; 4 | 5 | @import "node_modules/element-plus-admin/assets/styles/global/reset/root"; 6 | 7 | body { 8 | color: map.get($text-color, regular); 9 | background-color: #eff3f8; 10 | } 11 | -------------------------------------------------------------------------------- /src/router/routes/private/ads.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const adsRoute = { 4 | path: ":menu/ads", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/ads/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @include c(upload) { 4 | @include e(tip) { 5 | @include text--middle(20px); 6 | } 7 | 8 | @include e(progress) { 9 | .el-progress__text { 10 | min-width: 20px !important; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/router/routes/private/articles.js: -------------------------------------------------------------------------------- 1 | export const articlesRoute = { 2 | path: ":menu/articles-:path", 3 | component: () => import("@/views/articles/index.vue"), 4 | children: [ 5 | { 6 | path: "", 7 | component: () => import("@/views/articles/list/index.vue"), 8 | }, 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /src/router/routes/private/jobs.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const jobsRoute = { 4 | path: ":menu/jobs", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/jobs/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes/private/users.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const usersRoute = { 4 | path: ":menu/users", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/users/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes/private/orders.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const ordersRoute = { 4 | path: ":menu/orders", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/orders/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes/private/researches.js: -------------------------------------------------------------------------------- 1 | export const researchesRoute = { 2 | path: ":menu/researches-:path", 3 | component: () => import("@/views/researches/index.vue"), 4 | children: [ 5 | { 6 | path: "", 7 | component: () => import("@/views/researches/list/index.vue"), 8 | }, 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/components/image/script.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "ListImage", 3 | props: { 4 | src: String, 5 | width: { 6 | type: String, 7 | default: "100px", 8 | }, 9 | height: { 10 | type: String, 11 | default: "100px", 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/router/routes/private/products.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const productsRoute = { 4 | path: ":menu/products", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/products/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes/private/team-members.js: -------------------------------------------------------------------------------- 1 | export const teamMembersRoute = { 2 | path: ":menu/team-members-:path", 3 | component: () => import("@/views/team-members/index.vue"), 4 | children: [ 5 | { 6 | path: "", 7 | component: () => import("@/views/team-members/list/index.vue"), 8 | }, 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/components/image/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include b(image) { 6 | border-radius: 4px; 7 | box-shadow: 0 0 6px 2px rgba(168, 168, 171, 0.16); 8 | 9 | &:hover { 10 | box-shadow: 0 0 6px 2px rgba(168, 168, 171, 0.26); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/router/routes/private/categories.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const categoriesRoute = { 4 | path: ":menu/categories", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/categories/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes/private/app-upgrades.js: -------------------------------------------------------------------------------- 1 | import { RouterView } from "vue-router"; 2 | 3 | export const appUpgradesRoute = { 4 | path: ":menu/app-upgrades", 5 | component: , 6 | children: [ 7 | { 8 | path: "", 9 | component: () => import("@/views/app-upgrades/list/index.vue"), 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/router/routes/public/login.js: -------------------------------------------------------------------------------- 1 | import { store } from "@/store"; 2 | 3 | export const loginRoute = { 4 | path: "/login", 5 | component: () => import("@/views/login/index.vue"), 6 | beforeEnter(to, from, next) { 7 | if (store.state.auth.user.token) { 8 | next("/"); 9 | } else { 10 | next(); 11 | } 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /alias.config.js: -------------------------------------------------------------------------------- 1 | // WebStorm IDE:File -> Settings -> Languages & Frameworks -> JavaScript -> Webpack 2 | // -> webpack configuration file 选择 alias.config.js 3 | 4 | const resolve = (dir) => require("path").join(__dirname, dir); 5 | 6 | module.exports = { 7 | resolve: { 8 | alias: { 9 | "@": resolve("./src"), 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/components/office-viewer/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include c(office-viewer) { 6 | .el-dialog__body { 7 | padding: 0 !important; 8 | } 9 | 10 | @include e(iframe) { 11 | @include size(100%, 700px); 12 | 13 | border: 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/components/video-viewer/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include c(video-viewer) { 6 | .el-dialog__body { 7 | padding: 0 !important; 8 | } 9 | 10 | @include e(video) { 11 | @include size(100%, 540px); 12 | 13 | border: 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/composables/use-helpers.js: -------------------------------------------------------------------------------- 1 | import helpers from "jt-helpers"; 2 | import { useConsts } from "@/composables/use-consts"; 3 | 4 | export const useHelpers = () => { 5 | const { ApiUrl } = useConsts(); 6 | 7 | return { 8 | ...helpers, 9 | getFileUrl({ id }) { 10 | return `${ApiUrl}/public/files/${id}`; 11 | }, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // publicPath: "/fjqshadmin/", 3 | transpileDependencies: ["element-plus-admin"], 4 | configureWebpack: { 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.mjs$/, 9 | include: /node_modules/, 10 | type: "javascript/auto", 11 | }, 12 | ], 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | import time from "jt-time"; 2 | import { useConsts } from "@/composables/use-consts"; 3 | import { useHelpers } from "@/composables/use-helpers"; 4 | 5 | export const config = (app) => { 6 | app.config.globalProperties.$time = time; 7 | app.config.globalProperties.$consts = useConsts(); 8 | app.config.globalProperties.$helpers = useHelpers(); 9 | }; 10 | -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/element-plus/reset.scss: -------------------------------------------------------------------------------- 1 | .el-dialog { 2 | &__body { 3 | padding: 20px !important; 4 | padding-bottom: 10px !important; 5 | } 6 | } 7 | 8 | .el-form-item { 9 | &__content { 10 | display: block !important; 11 | } 12 | } 13 | 14 | .el-form--inline { 15 | .el-form-item { 16 | margin-right: 10px !important; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/components/image/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/styles/global/index.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @import "~sass-utils/global/reset/base"; 4 | @import "~sass-utils/global/utils/base"; 5 | @import "~sass-utils/pc/global"; 6 | @import "~sass-utils/ui/element-plus/global"; 7 | 8 | @import "./classes"; 9 | @import "./components"; 10 | @import "../element-plus"; 11 | @import "./reset"; 12 | @import "./utils/base"; 13 | -------------------------------------------------------------------------------- /src/router/routes/public/logout.js: -------------------------------------------------------------------------------- 1 | import { store } from "@/store"; 2 | import { RouterView } from "vue-router"; 3 | 4 | export const logoutRoute = { 5 | path: "/logout", 6 | component: , 7 | async beforeEnter(to, from, next) { 8 | if (store.state.auth.user.token) { 9 | await store.dispatch("auth/logout"); 10 | } 11 | next("/login"); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/layout/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/main/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/classes/base-colors.scss: -------------------------------------------------------------------------------- 1 | @each $key, $value in $base-colors { 2 | .t-#{"" + $key} { 3 | color: $value !important; 4 | } 5 | 6 | .bg-#{"" + $key} { 7 | background-color: $value !important; 8 | } 9 | 10 | .bd-#{"" + $key} { 11 | border: 1px solid $value !important; 12 | } 13 | 14 | .bdb-#{"" + $key} { 15 | border-bottom: 1px solid $value !important; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/composables/use-consts.js: -------------------------------------------------------------------------------- 1 | export const useConsts = () => { 2 | // 基础地址 3 | const BaseUrl = process.env["BASE_URL"]; 4 | 5 | // 接口地址 6 | const ApiUrl = process.env["VUE_APP_API_URL"]; 7 | 8 | // 静态文件地址 9 | const StaticUrl = process.env["VUE_APP_STATIC_URL"]; 10 | 11 | // 分页大小 12 | const PageSize = 10; 13 | 14 | return { 15 | BaseUrl, 16 | ApiUrl, 17 | StaticUrl, 18 | PageSize, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /npm/element-plus-admin/composables/use-xlsx.js: -------------------------------------------------------------------------------- 1 | import * as Xlsx from "xlsx"; 2 | 3 | export const useXlsx = () => { 4 | const save = ({ fileName, data } = {}) => { 5 | const worksheet = Xlsx.utils.json_to_sheet(data); 6 | const workbook = Xlsx.utils.book_new(); 7 | 8 | Xlsx.utils.book_append_sheet(workbook, worksheet, "Sheet"); 9 | Xlsx.writeFile(workbook, `${fileName}.xlsx`); 10 | }; 11 | 12 | return { 13 | save, 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/items/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/enum-select/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/views/orders/list/components/detail/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include b(detail) { 6 | max-height: 450px; 7 | overflow-x: hidden; 8 | overflow-y: auto; 9 | padding-right: 4px; 10 | 11 | li { 12 | @include padding(6px, null, 6px, 90px); 13 | 14 | position: relative; 15 | line-height: 150%; 16 | } 17 | 18 | label { 19 | @include position--absolute(10px, null, null, 0); 20 | 21 | line-height: 100%; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/global/components/scrollbar.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @include c(scrollbar) { 4 | &::-webkit-scrollbar { 5 | width: 10px; 6 | height: 10px; 7 | background-color: white; 8 | } 9 | 10 | &::-webkit-scrollbar-thumb { 11 | background-color: map.get($border-color, light); 12 | border-radius: 5px; 13 | } 14 | 15 | &::-webkit-scrollbar-track { 16 | background-color: map.get($border-color, extra-light); 17 | border-radius: 5px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/editor/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/views/login/components/form/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @include b(form) { 4 | width: 280px; 5 | 6 | .el-form-item { 7 | margin-bottom: 24px; 8 | } 9 | 10 | @include e(wrap) { 11 | @include position--absolute(50%, 50px); 12 | @include padding(44px, 36px, 24px, 36px); 13 | 14 | transform: translateY(-50%); 15 | background: white; 16 | } 17 | 18 | @include e(title) { 19 | padding-bottom: 30px; 20 | line-height: 100%; 21 | font-size: 20px; 22 | text-align: center; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "vuex"; 2 | import createPersistedState from "vuex-persistedstate"; 3 | import storage from "jt-storage"; 4 | import modules from "./modules"; 5 | 6 | export const store = createStore({ 7 | plugins: [ 8 | createPersistedState({ 9 | paths: ["enums", "auth"], 10 | storage: { 11 | getItem: (key) => storage.get(key), 12 | setItem: (key, value) => storage.set(key, value), 13 | removeItem: (key) => storage.remove(key), 14 | }, 15 | }), 16 | ], 17 | modules, 18 | }); 19 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/components/video-viewer/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/header/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/store/enums.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia/dist/pinia.cjs"; 2 | import { ref } from "vue"; 3 | import { publicEnumsApi } from "element-plus-admin/apis/public/enums"; 4 | 5 | export const useEnumsStore = defineStore( 6 | "enums", 7 | () => { 8 | const data = ref({ 9 | config: { 10 | version: "", 11 | }, 12 | }); 13 | 14 | const get = async () => { 15 | data.value = await publicEnumsApi.get({}); 16 | }; 17 | 18 | return { 19 | data, 20 | get, 21 | }; 22 | }, 23 | { persist: true } 24 | ); 25 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-list/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/login/script.js: -------------------------------------------------------------------------------- 1 | import CLogin from "element-plus-admin/components/login/index.vue"; 2 | import CLoginHeader from "element-plus-admin/components/login/components/header/index.vue"; 3 | import CLoginMain from "element-plus-admin/components/login/components/main/index.vue"; 4 | import CLoginFooter from "element-plus-admin/components/login/components/footer/index.vue"; 5 | import BForm from "./components/form/index.vue"; 6 | 7 | export default { 8 | components: { 9 | CLogin, 10 | CLoginHeader, 11 | CLoginMain, 12 | CLoginFooter, 13 | BForm, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /npm/element-plus-admin/assets/styles/element-plus/components.scss: -------------------------------------------------------------------------------- 1 | @import "~sass-utils"; 2 | 3 | @include c(date-picker) { 4 | @include m(full) { 5 | width: 100% !important; 6 | } 7 | 8 | @include m(two-days) { 9 | width: 360px !important; 10 | } 11 | } 12 | 13 | @include c(input-number) { 14 | @include m(full) { 15 | width: 100% !important; 16 | } 17 | } 18 | 19 | @include c(select) { 20 | @include m(full) { 21 | width: 100% !important; 22 | } 23 | } 24 | 25 | @include c(button) { 26 | @include m(full) { 27 | width: 100% !important; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/views/orders/list/components/detail/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | 4 | export default { 5 | setup() { 6 | const { enums } = useEnums(); 7 | 8 | const cDialog = reactive({ 9 | visible: false, 10 | }); 11 | 12 | const detail = ref({}); 13 | 14 | const show = (data) => { 15 | detail.value = data; 16 | cDialog.visible = true; 17 | }; 18 | 19 | return { 20 | enums, 21 | cDialog, 22 | detail, 23 | show, 24 | }; 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/assets/styles/utils/variables.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "../element-plus/variables"; 4 | 5 | $font-sizes: $pxes; 6 | 7 | $base-colors: ( 8 | white: white, 9 | black: black, 10 | primary: map.get($colors, primary, base), 11 | success: map.get($colors, success, base), 12 | warning: map.get($colors, warning, base), 13 | danger: map.get($colors, danger, base), 14 | info: map.get($colors, info, base), 15 | secondary: #409eff, 16 | ); 17 | 18 | $the-layout: ( 19 | the-header: ( 20 | height: 60px, 21 | ), 22 | the-sidebar: ( 23 | width: 210px, 24 | ), 25 | ); 26 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/components/office-viewer/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/components/video-viewer/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, watch } from "vue"; 2 | 3 | export default { 4 | setup() { 5 | const cDialog = reactive({ 6 | visible: false, 7 | src: "", 8 | }); 9 | 10 | watch( 11 | () => cDialog.visible, 12 | (newVal) => { 13 | if (!newVal) { 14 | cDialog.src = ""; 15 | } 16 | } 17 | ); 18 | 19 | const show = ({ src }) => { 20 | cDialog.visible = true; 21 | cDialog.src = src; 22 | }; 23 | 24 | return { 25 | cDialog, 26 | show, 27 | }; 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /npm/element-plus-admin/composables/use-enums.js: -------------------------------------------------------------------------------- 1 | import { computed } from "vue"; 2 | import { useStore } from "vuex"; 3 | import { publicEnumsApi } from "../apis/public/enums"; 4 | 5 | export const useEnums = () => { 6 | const { dispatch, state } = useStore(); 7 | 8 | const enums = computed(() => state["enums"].data); 9 | 10 | const getEnums = async () => { 11 | const { version } = await publicEnumsApi.post({ 12 | action: "getVersion", 13 | }); 14 | 15 | if (version !== enums.value.config.version) { 16 | await dispatch("enums/get"); 17 | } 18 | }; 19 | 20 | return { enums, getEnums }; 21 | }; 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 后台管理系统 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/header/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include b(header) { 6 | @include position--fixed(0, null, null, 0); 7 | @include size(100%, 80px); 8 | 9 | @include c(container) { 10 | height: 100%; 11 | } 12 | 13 | @include e(logo) { 14 | @include position--absolute(50%, null, null, 0); 15 | @include text--middle(50px); 16 | 17 | transform: translateY(-50%); 18 | padding-left: 60px; 19 | 20 | img { 21 | @include position--absolute(0, null, null, 0); 22 | @include size(50px, 50px); 23 | 24 | display: block; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /npm/element-plus-admin/store/modules/enums.js: -------------------------------------------------------------------------------- 1 | import helpers from "jt-helpers"; 2 | import { publicEnumsApi } from "../../apis/public/enums"; 3 | 4 | const state = { 5 | data: { 6 | config: { 7 | version: "", 8 | }, 9 | }, 10 | }; 11 | 12 | const types = helpers.keyMirror({ 13 | SetData: null, 14 | }); 15 | 16 | const mutations = { 17 | [types.SetData](state, data) { 18 | state.data = data; 19 | }, 20 | }; 21 | 22 | const actions = { 23 | async get({ commit }) { 24 | const res = await publicEnumsApi.get({}); 25 | commit(types.SetData, res); 26 | return res; 27 | }, 28 | }; 29 | 30 | export const enums = { 31 | namespaced: true, 32 | state, 33 | mutations, 34 | actions, 35 | }; 36 | -------------------------------------------------------------------------------- /npm/element-plus-admin/composables/use-auth.js: -------------------------------------------------------------------------------- 1 | import { computed } from "vue"; 2 | import { store } from "@/store"; 3 | 4 | export const useAuth = () => { 5 | const { dispatch, state } = store; 6 | 7 | const user = computed(() => state.auth.user); 8 | 9 | const menus = computed(() => state.auth.menus); 10 | 11 | const getHeaders = () => { 12 | return { Authorization: `Bearer ${user.value.token}` }; 13 | }; 14 | 15 | const loggedIn = () => !!user.value.token; 16 | 17 | const login = (options) => dispatch("auth/login", options); 18 | 19 | const logout = () => dispatch("auth/logout"); 20 | 21 | return { 22 | user, 23 | menus, 24 | getHeaders, 25 | loggedIn, 26 | login, 27 | logout, 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/login/components/main/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include b(main) { 6 | @include position--fixed(80px, null, 80px, 0); 7 | 8 | width: 100%; 9 | background: { 10 | image: url("./components/main/images/bg.png"); 11 | position: center center; 12 | size: auto 100%; 13 | } 14 | 15 | @include c(container) { 16 | height: 100%; 17 | } 18 | 19 | @include e(icon) { 20 | @include position--absolute(50%, null, null, 0); 21 | 22 | transform: translateY(-50%); 23 | 24 | h2 { 25 | padding-top: 50px; 26 | } 27 | 28 | img { 29 | @include size(520px * 0.85, 446px * 0.85); 30 | 31 | display: block; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/enum-select/script.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | placeholder: { 4 | type: String, 5 | default: "请选择", 6 | }, 7 | multiple: { 8 | type: Boolean, 9 | default: false, 10 | }, 11 | clearable: { 12 | type: Boolean, 13 | default: false, 14 | }, 15 | items: { 16 | type: Object, 17 | default: () => [], 18 | }, 19 | value: { 20 | type: [String, Number], 21 | }, 22 | }, 23 | emits: ["update:value", "change"], 24 | setup(props, context) { 25 | const onChange = (value) => { 26 | context.emit("update:value", value); 27 | context.emit("change", value); 28 | }; 29 | 30 | return { 31 | onChange, 32 | }; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { createPinia } from "pinia"; 3 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; 4 | import "@/assets/styles/element-plus/index.scss"; 5 | import ElementPlus from "element-plus"; 6 | import "@/assets/styles/global/index.scss"; 7 | import "element-plus-admin/utils/polyfills"; 8 | import { config } from "@/utils/config"; 9 | import { useComponents } from "@/utils/use-components"; 10 | import App from "./App.vue"; 11 | import { store } from "./store"; 12 | import { router } from "./router"; 13 | 14 | const pinia = createPinia(); 15 | const app = createApp(App); 16 | 17 | pinia.use(piniaPluginPersistedstate); 18 | 19 | app.use(pinia).use(store).use(router).use(ElementPlus).mount("#app"); 20 | 21 | config(app); 22 | useComponents(app); 23 | -------------------------------------------------------------------------------- /npm/element-plus-admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element-plus-admin", 3 | "version": "1.2.17", 4 | "description": "element-plus admin system", 5 | "main": "index.js", 6 | "author": "zhaojintian", 7 | "license": "ISC", 8 | "dependencies": { 9 | "@element-plus/icons-vue": "^2.1.0", 10 | "@wangeditor/editor": "^5.1.23", 11 | "@wangeditor/editor-for-vue": "^5.1.12", 12 | "ali-oss": "^6.17.1", 13 | "axios": "^1.4.0", 14 | "core-js": "^3.31.0", 15 | "cos-js-sdk-v5": "^1.4.18", 16 | "element-plus": "^2.3.7", 17 | "js-base64": "^3.7.5", 18 | "nprogress": "^0.2.0", 19 | "sass-utils": "^1.1.12", 20 | "throttle-debounce": "^5.0.0", 21 | "vue": "^3.3.4", 22 | "vue-router": "^4.2.2", 23 | "wangeditor": "^4.7.15", 24 | "xlsx": "^0.18.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/composables/use-helpers.js: -------------------------------------------------------------------------------- 1 | import { Base64 } from "js-base64"; 2 | 3 | export const useHelpers = () => { 4 | const formatFilters = (filters) => { 5 | delete filters.isTrusted; 6 | 7 | const ret = {}; 8 | 9 | Object.keys(filters).forEach((key) => { 10 | if ( 11 | typeof filters[key] === "string" || 12 | typeof filters[key] === "number" 13 | ) { 14 | ret[key] = { $eq: filters[key] }; 15 | } else { 16 | ret[key] = filters[key]; 17 | } 18 | }); 19 | 20 | return ret; 21 | }; 22 | 23 | const encode = (object) => Base64.encode(JSON.stringify(object)); 24 | 25 | const decode = (string) => 26 | string ? JSON.parse(Base64.decode(string.toString())) : {}; 27 | 28 | return { formatFilters, encode, decode }; 29 | }; 30 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/order-input/script.js: -------------------------------------------------------------------------------- 1 | import { debounce } from "lodash"; 2 | 3 | export default { 4 | props: { 5 | row: Object, 6 | api: Object, 7 | where: { 8 | type: Object, 9 | default: () => ({}), 10 | }, 11 | }, 12 | emits: ["ok"], 13 | setup(props, context) { 14 | const onChange = debounce(async (value) => { 15 | await props.api.post({ 16 | joinUrl: `/${props.row.id}/actions/order`, 17 | query: { 18 | where: { 19 | id: { $ne: 0 }, 20 | ...props.where, 21 | }, 22 | }, 23 | body: { 24 | action: "Update", 25 | order: value, 26 | }, 27 | }); 28 | 29 | context.emit("ok"); 30 | }, 500); 31 | 32 | return { 33 | onChange, 34 | }; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/store/user.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia/dist/pinia.cjs"; 2 | import { publicManagersApi } from "element-plus-admin/apis/public/managers"; 3 | import { ref } from "vue"; 4 | 5 | export const useUserStore = defineStore( 6 | "user", 7 | () => { 8 | const user = ref({}); 9 | const token = ref(""); 10 | 11 | const login = async (data) => { 12 | const res = await publicManagersApi.post({ action: "login", body: data }); 13 | 14 | user.value = { id: res.manager.id, name: res.manager.username }; 15 | token.value = res.token; 16 | }; 17 | 18 | const logout = () => { 19 | user.value = {}; 20 | token.value = ""; 21 | }; 22 | 23 | return { 24 | user, 25 | token, 26 | login, 27 | logout, 28 | }; 29 | }, 30 | { 31 | persist: true, 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /src/views/users/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { usersApi } from "@/apis/admin/users"; 4 | 5 | export default { 6 | setup() { 7 | const { enums } = useEnums(); 8 | 9 | const { list, currentPage, cFilters, reRender, onPageChange, search } = 10 | useList({ 11 | api: usersApi, 12 | filters: { 13 | model: { 14 | nickName: { $like: "" }, 15 | phoneNumber: { $like: "" }, 16 | }, 17 | rules: {}, 18 | }, 19 | }); 20 | 21 | return { 22 | usersApi, 23 | enums, 24 | list, 25 | currentPage, 26 | cFilters, 27 | reRender, 28 | onPageChange, 29 | search, 30 | }; 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/router/routes/private/index.js: -------------------------------------------------------------------------------- 1 | import { adsRoute } from "./ads"; 2 | import { appUpgradesRoute } from "./app-upgrades"; 3 | import { categoriesRoute } from "./categories"; 4 | import { homeRoute } from "./home"; 5 | import { ordersRoute } from "./orders"; 6 | import { productsRoute } from "./products"; 7 | import { usersRoute } from "./users"; 8 | 9 | import { articlesRoute } from "./articles"; 10 | import { contentRoute } from "./contents"; 11 | import { jobsRoute } from "./jobs"; 12 | import { researchesRoute } from "./researches"; 13 | import { teamMembersRoute } from "./team-members"; 14 | 15 | export const privateRoutes = [ 16 | adsRoute, 17 | appUpgradesRoute, 18 | categoriesRoute, 19 | homeRoute, 20 | ordersRoute, 21 | productsRoute, 22 | usersRoute, 23 | 24 | articlesRoute, 25 | contentRoute, 26 | jobsRoute, 27 | researchesRoute, 28 | teamMembersRoute, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/views/contents/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/components/office-viewer/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, watch } from "vue"; 2 | 3 | export default { 4 | props: { 5 | serviceUrl: String, 6 | }, 7 | setup(props) { 8 | const cDialog = reactive({ 9 | visible: false, 10 | serviceUrl: 11 | props.serviceUrl || 12 | "https://view.officeapps.live.com/op/view.aspx?src=", 13 | src: "", 14 | }); 15 | 16 | watch( 17 | () => cDialog.visible, 18 | (newVal) => { 19 | if (!newVal) { 20 | cDialog.src = ""; 21 | } 22 | } 23 | ); 24 | 25 | const show = ({ serviceUrl, src }) => { 26 | cDialog.visible = true; 27 | 28 | if (serviceUrl) { 29 | cDialog.serviceUrl = serviceUrl; 30 | } 31 | 32 | cDialog.src = src; 33 | }; 34 | 35 | return { 36 | cDialog, 37 | show, 38 | }; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/components/layout/script.js: -------------------------------------------------------------------------------- 1 | import TheHeader from "element-plus-admin/components/layout/header/index.vue"; 2 | import TheSidebar from "element-plus-admin/components/layout/sidebar/index.vue"; 3 | import TheMain from "element-plus-admin/components/layout/main/index.vue"; 4 | import { ElMessage } from "element-plus"; 5 | import { useRouter } from "vue-router"; 6 | import { useAuth } from "element-plus-admin/composables/use-auth"; 7 | import { getMenus } from "./utils/get-menus"; 8 | 9 | export default { 10 | components: { 11 | TheHeader, 12 | TheSidebar, 13 | TheMain, 14 | }, 15 | setup() { 16 | const router = useRouter(); 17 | const { user, logout: userLogout } = useAuth(); 18 | 19 | const logout = async () => { 20 | await userLogout(); 21 | await router.push("/login"); 22 | ElMessage.success("退出成功"); 23 | }; 24 | 25 | return { 26 | user, 27 | getMenus, 28 | logout, 29 | }; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/resource-select/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@/assets/styles/utils"; 2 | 3 | @include c(list) { 4 | @import "./components/image/style"; 5 | 6 | @include e(header) { 7 | @include clearfix; 8 | 9 | .el-form-item { 10 | margin-bottom: 18px !important; 11 | } 12 | 13 | @include s(is-simple) { 14 | @include c(list) { 15 | @include e(operations) { 16 | margin-bottom: 18px; 17 | float: left; 18 | } 19 | 20 | @include e(filters) { 21 | float: right; 22 | margin-right: -10px; 23 | } 24 | } 25 | } 26 | } 27 | 28 | @include e(operations) { 29 | margin-bottom: 10px; 30 | } 31 | 32 | @include e(filters) { 33 | .el-select, 34 | .el-input { 35 | width: 200px !important; 36 | } 37 | } 38 | 39 | @include e(pagination) { 40 | padding-top: 10px; 41 | position: relative; 42 | text-align: right; 43 | right: -9px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/header/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include c(header) { 6 | @include text--middle(map.get($the-layout, the-header, height)); 7 | 8 | position: relative; 9 | background-color: white; 10 | 11 | @include e(logo) { 12 | @include position--absolute(50%, null, null, 20px); 13 | @include text--middle(36px); 14 | 15 | transform: translateY(-50%); 16 | padding-left: 46px; 17 | font-size: 20px; 18 | font-weight: bold; 19 | cursor: pointer; 20 | 21 | img { 22 | @include position--absolute(0, null, null, 0); 23 | @include size(36px, 36px); 24 | 25 | display: block; 26 | } 27 | } 28 | 29 | @include e(user) { 30 | @include position--absolute(0, 20px); 31 | @include text--middle(map.get($the-layout, the-header, height)); 32 | 33 | padding-right: 50px; 34 | 35 | .el-avatar { 36 | @include position--absolute(50%, 0); 37 | 38 | transform: translateY(-50%); 39 | cursor: pointer; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/use-components.js: -------------------------------------------------------------------------------- 1 | import CEditor from "element-plus-admin/components/editor"; 2 | import CEnumSelect from "element-plus-admin/components/enum-select/index.vue"; 3 | import CList from "element-plus-admin/components/list/index.vue"; 4 | import CListImage from "element-plus-admin/components/list/components/image/index.vue"; 5 | import COrderInput from "element-plus-admin/components/order-input/index.vue"; 6 | import CResourceSelect from "element-plus-admin/components/resource-select/index.vue"; 7 | import CUpload from "element-plus-admin/components/upload/index.vue"; 8 | import { Eleme, Right } from "@element-plus/icons-vue"; 9 | 10 | export const useComponents = (app) => { 11 | app.component("c-editor", CEditor); 12 | app.component("c-enum-select", CEnumSelect); 13 | app.component("c-list", CList); 14 | app.component("c-list-image", CListImage); 15 | app.component("c-order-input", COrderInput); 16 | app.component("c-resource-select", CResourceSelect); 17 | app.component("c-upload", CUpload); 18 | app.component("el-icon-right", Right); 19 | app.component("el-icon-eleme", Eleme); 20 | }; 21 | -------------------------------------------------------------------------------- /src/views/ads/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { ref } from "vue"; 4 | import { adsApi } from "@/apis/admin/ads"; 5 | import BForm from "./components/form/index.vue"; 6 | 7 | export default { 8 | components: { 9 | BForm, 10 | }, 11 | setup() { 12 | const formRef = ref(null); 13 | 14 | const { enums } = useEnums(); 15 | 16 | const { list, currentPage, cFilters, reRender, onPageChange, search, del } = 17 | useList({ 18 | api: adsApi, 19 | filters: { 20 | model: { 21 | title: { $like: "" }, 22 | }, 23 | rules: {}, 24 | }, 25 | extraQuery: { 26 | order: [["order", "DESC"]], 27 | }, 28 | }); 29 | 30 | return { 31 | adsApi, 32 | formRef, 33 | enums, 34 | list, 35 | currentPage, 36 | cFilters, 37 | reRender, 38 | onPageChange, 39 | search, 40 | del, 41 | }; 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/views/jobs/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { ref } from "vue"; 4 | import { jobsApi } from "@/apis/admin/jobs"; 5 | import BForm from "./components/form/index.vue"; 6 | 7 | export default { 8 | components: { 9 | BForm, 10 | }, 11 | setup() { 12 | const formRef = ref(null); 13 | 14 | const { enums } = useEnums(); 15 | 16 | const { list, currentPage, cFilters, reRender, onPageChange, search, del } = 17 | useList({ 18 | api: jobsApi, 19 | filters: { 20 | model: { 21 | name: { $like: "" }, 22 | }, 23 | rules: {}, 24 | }, 25 | extraQuery: { 26 | order: [["order", "DESC"]], 27 | }, 28 | }); 29 | 30 | return { 31 | jobsApi, 32 | formRef, 33 | enums, 34 | list, 35 | currentPage, 36 | cFilters, 37 | reRender, 38 | onPageChange, 39 | search, 40 | del, 41 | }; 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/views/ads/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/files/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/files/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include cc(files) { 6 | padding-top: 4px; 7 | 8 | @include e(item) { 9 | @include text--middle(32px); 10 | 11 | position: relative; 12 | border-radius: 4px; 13 | cursor: pointer; 14 | 15 | &:hover { 16 | background-color: #f5f7fa; 17 | 18 | @include cc(files) { 19 | @include e(status) { 20 | display: none; 21 | } 22 | 23 | @include e(close) { 24 | display: block; 25 | } 26 | } 27 | } 28 | } 29 | 30 | @include e(icon, status, close, name) { 31 | @include position--absolute(50%); 32 | 33 | transform: translateY(-50%); 34 | } 35 | 36 | @include e(icon) { 37 | left: 10px; 38 | } 39 | 40 | @include e(status, close) { 41 | right: 10px; 42 | } 43 | 44 | @include e(status) { 45 | color: map.get($colors, success, base) !important; 46 | } 47 | 48 | @include e(close) { 49 | display: none; 50 | } 51 | 52 | @include e(name) { 53 | left: 30px; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/views/categories/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/views/orders/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { ordersApi } from "@/apis/admin/orders"; 4 | import BDetail from "./components/detail/index.vue"; 5 | import { ref } from "vue"; 6 | 7 | export default { 8 | components: { 9 | BDetail, 10 | }, 11 | setup() { 12 | const { enums } = useEnums(); 13 | 14 | const { list, currentPage, cFilters, reRender, onPageChange, search, del } = 15 | useList({ 16 | api: ordersApi, 17 | filters: { 18 | model: { 19 | no: { $like: "" }, 20 | }, 21 | rules: {}, 22 | }, 23 | extraQuery: { 24 | include: [{ model: "User", as: "user" }], 25 | }, 26 | }); 27 | 28 | const detail = ref(null); 29 | 30 | return { 31 | ordersApi, 32 | enums, 33 | list, 34 | currentPage, 35 | cFilters, 36 | detail, 37 | reRender, 38 | onPageChange, 39 | search, 40 | del, 41 | }; 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | import { publicRoutes } from "./routes/public"; 3 | import { privateRoutes } from "./routes/private"; 4 | import { store } from "@/store"; 5 | 6 | const routes = [ 7 | { 8 | path: "/", 9 | component: () => import("@/components/layout/index.vue"), 10 | children: privateRoutes, 11 | meta: { 12 | requiresAuth: true, 13 | }, 14 | }, 15 | ...publicRoutes, 16 | { 17 | path: "/:matchOthers(.*)*", 18 | component: () => import("@/views/404/index.vue"), 19 | }, 20 | ]; 21 | 22 | const router = createRouter({ 23 | history: createWebHashHistory(), 24 | routes, 25 | }); 26 | 27 | router.beforeEach(async (to, from, next) => { 28 | if (to.matched.some((record) => record.meta.requiresAuth)) { 29 | if (!store.state.auth.user.token) { 30 | await store.dispatch("auth/logout"); 31 | 32 | next({ 33 | path: "login", 34 | query: { redirect: to.fullPath }, 35 | }); 36 | } else { 37 | next(); 38 | } 39 | } else { 40 | next(); 41 | } 42 | }); 43 | 44 | export { router }; 45 | -------------------------------------------------------------------------------- /src/views/app-upgrades/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { ref } from "vue"; 4 | import { appUpgradesApi } from "@/apis/admin/app-upgrades"; 5 | import BForm from "./components/form/index.vue"; 6 | 7 | export default { 8 | components: { 9 | BForm, 10 | }, 11 | setup() { 12 | const formRef = ref(null); 13 | 14 | const { enums } = useEnums(); 15 | 16 | const { list, currentPage, cFilters, reRender, onPageChange, search, del } = 17 | useList({ 18 | api: appUpgradesApi, 19 | filters: { 20 | model: { 21 | versionName: { $like: "" }, 22 | versionCode: { $like: "" }, 23 | status: undefined, 24 | }, 25 | rules: {}, 26 | }, 27 | }); 28 | 29 | return { 30 | appUpgradesApi, 31 | formRef, 32 | enums, 33 | list, 34 | currentPage, 35 | cFilters, 36 | reRender, 37 | onPageChange, 38 | search, 39 | del, 40 | }; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /npm/element-plus-admin/store/modules/items.js: -------------------------------------------------------------------------------- 1 | import { keyMirror } from "jt-helpers"; 2 | 3 | const state = { 4 | data: {}, 5 | }; 6 | 7 | const types = keyMirror({ 8 | SetData: null, 9 | }); 10 | 11 | const mutations = { 12 | [types.SetData](state, { resource, key, items }) { 13 | state.data[resource] = { 14 | ...state.data[resource], 15 | [key]: items, 16 | }; 17 | }, 18 | }; 19 | 20 | const actions = { 21 | async getItems({ state, commit }, { resource, api, ids }) { 22 | const key = ids ? ids.join(",") : "__"; 23 | 24 | if (state.data[resource] && state.data[resource][key]) { 25 | return state.data[resource][key]; 26 | } else { 27 | state.data[resource] = { 28 | ...state.data[resource], 29 | [key]: [], 30 | }; 31 | 32 | const { items } = await api.post({ 33 | action: "getAllByIds", 34 | body: { ids }, 35 | }); 36 | 37 | commit(types.SetData, { resource, key, items }); 38 | 39 | return items; 40 | } 41 | }, 42 | }; 43 | 44 | export const items = { 45 | namespaced: true, 46 | state, 47 | mutations, 48 | actions, 49 | }; 50 | -------------------------------------------------------------------------------- /src/views/categories/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { ref } from "vue"; 4 | import { categoriesApi } from "@/apis/admin/categories"; 5 | import BForm from "./components/form/index.vue"; 6 | 7 | export default { 8 | components: { 9 | BForm, 10 | }, 11 | setup() { 12 | const formRef = ref(null); 13 | 14 | const { enums } = useEnums(); 15 | 16 | const { list, currentPage, cFilters, reRender, onPageChange, search, del } = 17 | useList({ 18 | api: categoriesApi, 19 | filters: { 20 | model: { 21 | name: { $like: "" }, 22 | cnName: { $like: "" }, 23 | }, 24 | rules: {}, 25 | }, 26 | extraQuery: { 27 | order: [["order", "ASC"]], 28 | }, 29 | }); 30 | 31 | return { 32 | categoriesApi, 33 | formRef, 34 | enums, 35 | list, 36 | currentPage, 37 | cFilters, 38 | reRender, 39 | onPageChange, 40 | search, 41 | del, 42 | }; 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/main/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include c(main) { 6 | @include position--absolute( 7 | map.get($the-layout, the-header, height) + 14px, 8 | 0, 9 | 14px, 10 | map.get($the-layout, the-sidebar, width) + 14px * 2 11 | ); 12 | 13 | overflow: hidden; 14 | min-width: 1200px; 15 | 16 | @include e(body) { 17 | @include position--absolute(0, 14px, 0, 0); 18 | @include padding(50px + 12px, 20px, 20px, 20px); 19 | 20 | background-color: white; 21 | overflow-x: hidden; 22 | overflow-y: auto; 23 | 24 | &::-webkit-scrollbar { 25 | width: 10px; 26 | background-color: white; 27 | } 28 | 29 | &::-webkit-scrollbar-track { 30 | background-color: white; 31 | } 32 | 33 | &::-webkit-scrollbar-thumb { 34 | background-color: #f6f7fb; 35 | } 36 | } 37 | 38 | @include e(breadcrumb) { 39 | @include position--absolute(0, 14px + 20px, null, 20px); 40 | @include text--middle(50px); 41 | 42 | background-color: white; 43 | border-bottom: 1px solid map.get($border-color, light); 44 | z-index: 1; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/sidebar/style.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | @import "~@/assets/styles/utils"; 4 | 5 | @include c(sidebar) { 6 | @include position--absolute( 7 | map.get($the-layout, the-header, height) + 14px, 8 | null, 9 | 14px, 10 | 14px 11 | ); 12 | 13 | z-index: 1; 14 | width: map.get($the-layout, the-sidebar, width); 15 | background-color: map.get($base-colors, primary); 16 | overflow-x: hidden; 17 | overflow-y: auto; 18 | 19 | &::-webkit-scrollbar { 20 | width: 0; 21 | } 22 | 23 | .el-menu { 24 | width: map.get($the-layout, the-sidebar, width) + 2px; 25 | } 26 | 27 | .el-sub-menu__title, 28 | .el-menu > .el-menu-item { 29 | @include c(iconfont) { 30 | font-size: 16px; 31 | color: white; 32 | margin-right: 10px; 33 | } 34 | } 35 | 36 | .el-sub-menu .el-menu-item { 37 | padding-left: 46px !important; 38 | } 39 | 40 | .el-menu-item-group__title { 41 | display: none; 42 | } 43 | 44 | .el-menu-item { 45 | &.is-active { 46 | background-color: white; 47 | 48 | i { 49 | color: map.get($base-colors, primary) !important; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/assets/styles/global/components/iconfont.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "c-iconfont"; /* Project id 3325521 */ 3 | src: url("//at.alicdn.com/t/c/font_3325521_drrl7vh19ye.woff2?t=1664148146521") 4 | format("woff2"), 5 | url("//at.alicdn.com/t/c/font_3325521_drrl7vh19ye.woff?t=1664148146521") 6 | format("woff"), 7 | url("//at.alicdn.com/t/c/font_3325521_drrl7vh19ye.ttf?t=1664148146521") 8 | format("truetype"); 9 | } 10 | 11 | .c-iconfont { 12 | font-family: "c-iconfont" !important; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .c-iconfont--job:before { 19 | content: "\e647"; 20 | } 21 | 22 | .c-iconfont--other:before { 23 | content: "\e629"; 24 | } 25 | 26 | .c-iconfont--information:before { 27 | content: "\e8ea"; 28 | } 29 | 30 | .c-iconfont--talent:before { 31 | content: "\e60d"; 32 | } 33 | 34 | .c-iconfont--prize:before { 35 | content: "\e671"; 36 | } 37 | 38 | .c-iconfont--research:before { 39 | content: "\e677"; 40 | } 41 | 42 | .c-iconfont--introduction:before { 43 | content: "\e685"; 44 | } 45 | 46 | .c-iconfont--team:before { 47 | content: "\e62d"; 48 | } 49 | -------------------------------------------------------------------------------- /src/views/login/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useStore } from "vuex"; 3 | import { useRouter } from "vue-router"; 4 | import { ElMessage } from "element-plus"; 5 | import { useValidators } from "vue-validation"; 6 | import { UserFilled } from "@element-plus/icons-vue"; 7 | 8 | export default { 9 | setup() { 10 | const { dispatch } = useStore(); 11 | const router = useRouter(); 12 | const { isRequired } = useValidators(); 13 | 14 | const formRef = ref(null); 15 | 16 | const cForm = reactive({ 17 | model: {}, 18 | rules: { 19 | username: [isRequired({ label: "用户名" })], 20 | password: [isRequired({ label: "密码" })], 21 | }, 22 | }); 23 | 24 | const submit = () => { 25 | formRef.value.validate(async (valid) => { 26 | if (valid) { 27 | await dispatch("auth/login", cForm.model); 28 | ElMessage.success("登录成功"); 29 | await dispatch("auth/getMenus"); 30 | await router.push("/"); 31 | } 32 | }); 33 | }; 34 | 35 | return { 36 | UserFilled, 37 | formRef, 38 | cForm, 39 | submit, 40 | }; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/script.js: -------------------------------------------------------------------------------- 1 | import { useConsts } from "@/composables/use-consts"; 2 | import { ref, watch } from "vue"; 3 | 4 | export default { 5 | name: "List", 6 | emits: ["page-change", "page-size-change"], 7 | props: { 8 | total: { 9 | type: Number, 10 | default: 0, 11 | }, 12 | pageSize: { 13 | type: Number, 14 | default: useConsts().PageSize, 15 | }, 16 | currentPage: { 17 | type: Number, 18 | default: 1, 19 | }, 20 | showHeader: { 21 | type: Boolean, 22 | default: true, 23 | }, 24 | showPagination: { 25 | type: Boolean, 26 | default: true, 27 | }, 28 | simpleHeader: { 29 | type: Boolean, 30 | default: true, 31 | }, 32 | showPageSizes: { 33 | type: Boolean, 34 | default: false, 35 | }, 36 | }, 37 | setup(props) { 38 | const currentPageSize = ref(props.pageSize); 39 | 40 | watch( 41 | () => props.pageSize, 42 | (newVal) => { 43 | currentPageSize.value = newVal; 44 | }, 45 | { deep: true, immediate: true } 46 | ); 47 | 48 | return { 49 | currentPageSize, 50 | }; 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /npm/element-plus-admin/store/modules/auth.js: -------------------------------------------------------------------------------- 1 | import helpers from "jt-helpers"; 2 | import { publicManagersApi } from "../../apis/public/managers"; 3 | 4 | const state = { 5 | user: { 6 | id: "", 7 | name: "", 8 | token: "", 9 | }, 10 | menus: [], 11 | }; 12 | 13 | const types = helpers.keyMirror({ 14 | SetUser: null, 15 | SetMenus: null, 16 | }); 17 | 18 | const mutations = { 19 | [types.SetUser](state, user) { 20 | state.user = user; 21 | }, 22 | [types.SetMenus](state, menus) { 23 | state.menus = menus; 24 | }, 25 | }; 26 | 27 | const actions = { 28 | async login({ commit }, data) { 29 | const res = await publicManagersApi.post({ action: "login", body: data }); 30 | 31 | const { 32 | manager: { id, username }, 33 | token, 34 | } = res; 35 | 36 | commit(types.SetUser, { id, name: username, token }); 37 | 38 | return res; 39 | }, 40 | 41 | async getMenus() { 42 | return null; 43 | }, 44 | logout({ commit }) { 45 | commit(types.SetUser, state.user); 46 | commit(types.SetMenus, state.menus); 47 | return null; 48 | }, 49 | }; 50 | 51 | export const auth = { 52 | namespaced: true, 53 | state, 54 | mutations, 55 | actions, 56 | }; 57 | -------------------------------------------------------------------------------- /src/views/products/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { ref } from "vue"; 4 | import BForm from "./components/form/index.vue"; 5 | import { productsApi } from "@/apis/admin/products"; 6 | import { categoriesApi } from "@/apis/admin/categories"; 7 | 8 | export default { 9 | components: { 10 | BForm, 11 | }, 12 | setup() { 13 | const formRef = ref(null); 14 | 15 | const { enums } = useEnums(); 16 | 17 | const { list, currentPage, cFilters, reRender, onPageChange, search, del } = 18 | useList({ 19 | api: productsApi, 20 | filters: { 21 | model: { 22 | categoryId: undefined, 23 | name: { $like: "" }, 24 | cnName: { $like: "" }, 25 | }, 26 | rules: {}, 27 | }, 28 | extraQuery: { 29 | include: [{ model: "Category", as: "category" }], 30 | }, 31 | }); 32 | 33 | return { 34 | categoriesApi, 35 | formRef, 36 | enums, 37 | list, 38 | currentPage, 39 | cFilters, 40 | reRender, 41 | onPageChange, 42 | search, 43 | del, 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/resource-select/script.js: -------------------------------------------------------------------------------- 1 | import { onMounted, ref } from "vue"; 2 | 3 | export default { 4 | props: { 5 | placeholder: { 6 | type: String, 7 | default: "请选择", 8 | }, 9 | multiple: { 10 | type: Boolean, 11 | default: false, 12 | }, 13 | clearable: { 14 | type: Boolean, 15 | default: false, 16 | }, 17 | filterable: { 18 | type: Boolean, 19 | default: false, 20 | }, 21 | optionLabel: { 22 | type: [String, Function], 23 | default: "name", 24 | }, 25 | api: { 26 | type: [Object, Function], 27 | default: () => null, 28 | }, 29 | value: { 30 | type: [String, Number], 31 | }, 32 | }, 33 | emits: ["update:value"], 34 | setup(props, context) { 35 | const list = ref({ 36 | items: [], 37 | }); 38 | 39 | const render = async () => { 40 | list.value = 41 | typeof props.api === "function" 42 | ? await props.api() 43 | : await props.api.get({}); 44 | }; 45 | 46 | onMounted(async () => { 47 | await render(); 48 | }); 49 | 50 | const onChange = (index) => { 51 | context.emit("update:value", index); 52 | }; 53 | 54 | return { 55 | list, 56 | render, 57 | onChange, 58 | }; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/items/script.js: -------------------------------------------------------------------------------- 1 | import { computed, watch } from "vue"; 2 | import { useStore } from "vuex"; 3 | 4 | export default { 5 | props: { 6 | ids: { 7 | type: Array, 8 | default: () => [], 9 | }, 10 | className: { 11 | type: String, 12 | default: "", 13 | }, 14 | labelKey: { 15 | type: String, 16 | default: "name", 17 | }, 18 | resource: String, 19 | api: Object, 20 | plain: { 21 | type: Boolean, 22 | default: false, 23 | }, 24 | joinString: { 25 | type: String, 26 | default: "、", 27 | }, 28 | }, 29 | setup(props) { 30 | const { state, dispatch } = useStore(); 31 | 32 | const items = computed(() => 33 | state.items.data[props.resource] 34 | ? state.items.data[props.resource][props.ids.join(",")] 35 | : {} 36 | ); 37 | 38 | watch( 39 | () => props.ids, 40 | async (newVal) => { 41 | if (newVal && newVal.length) { 42 | await dispatch("items/getItems", { 43 | resource: props.resource, 44 | api: props.api, 45 | ids: newVal, 46 | }); 47 | } else { 48 | items.value = []; 49 | } 50 | }, 51 | { immediate: true, deep: true } 52 | ); 53 | 54 | return { 55 | items, 56 | }; 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/components/layout/utils/get-menus.js: -------------------------------------------------------------------------------- 1 | export const getMenus = () => { 2 | return [ 3 | { 4 | name: "product", 5 | title: "商品管理", 6 | icon: "product-filled", 7 | children: [ 8 | { 9 | title: "商品列表", 10 | path: "/product/products", 11 | }, 12 | { 13 | title: "商品分类", 14 | path: "/product/categories", 15 | }, 16 | ], 17 | }, 18 | { 19 | name: "ad", 20 | title: "广告管理", 21 | icon: "ad-filled", 22 | children: [ 23 | { 24 | title: "广告列表", 25 | path: "/ad/ads", 26 | }, 27 | ], 28 | }, 29 | { 30 | name: "order", 31 | title: "订单管理", 32 | icon: "order-filled", 33 | children: [ 34 | { 35 | title: "订单列表", 36 | path: "/order/orders", 37 | }, 38 | ], 39 | }, 40 | { 41 | name: "user", 42 | title: "用户管理", 43 | icon: "user-filled", 44 | children: [ 45 | { 46 | title: "用户列表", 47 | path: "/user/users", 48 | }, 49 | ], 50 | }, 51 | { 52 | name: "app", 53 | title: "应用管理", 54 | icon: "app-filled", 55 | children: [ 56 | { 57 | title: "应用升级", 58 | path: "/app/app-upgrades", 59 | }, 60 | ], 61 | }, 62 | ]; 63 | }; 64 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/main/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/editor/composables/use-upload.js: -------------------------------------------------------------------------------- 1 | import { useCos } from "../../upload/composables/use-cos"; 2 | import { useConsts } from "@/composables/use-consts"; 3 | import { ElLoading, ElMessage } from "element-plus"; 4 | 5 | export const useUpload = ({ props }) => { 6 | const { ApiUrl } = useConsts(); 7 | 8 | const configEditor = async ({ editorConfig }) => { 9 | if (props.cosConfig) { 10 | const cos = useCos(props.cosConfig); 11 | 12 | const customUpload = async (file, insertFn) => { 13 | const loading = ElLoading.service({ 14 | text: "正在上传...", 15 | lock: true, 16 | background: "rgba(122, 122, 122, 0.1)", 17 | }); 18 | 19 | await cos.initialize(); 20 | 21 | const { id } = await cos.upload(file); 22 | insertFn(`${ApiUrl}/public/files/${id}`); 23 | 24 | loading.close(); 25 | ElMessage.success("上传成功"); 26 | }; 27 | 28 | editorConfig.MENU_CONF["uploadImage"] = { customUpload }; 29 | editorConfig.MENU_CONF["uploadVideo"] = { customUpload }; 30 | } else { 31 | const config = { 32 | server: props.uploadAction, 33 | fieldName: "file", 34 | maxNumberOfFiles: 1, 35 | headers: props.uploadHeaders, 36 | customInsert(res, insertFn) { 37 | insertFn(`${ApiUrl}/public/files/${res.data.id}`); 38 | }, 39 | }; 40 | 41 | editorConfig.MENU_CONF["uploadImage"] = config; 42 | editorConfig.MENU_CONF["uploadVideo"] = config; 43 | } 44 | }; 45 | 46 | return { 47 | configEditor, 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/files/script.js: -------------------------------------------------------------------------------- 1 | import { computed, ref, watch } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { CircleCheck, Close, Document } from "@element-plus/icons-vue"; 4 | import FileViewer from "../../file-viewer/index.vue"; 5 | import { publicFilesApi } from "../../../apis/public/files"; 6 | import { useStore } from "vuex"; 7 | 8 | export default { 9 | components: { 10 | "el-icon-document": Document, 11 | "el-circle-check": CircleCheck, 12 | "el-close": Close, 13 | "c-file-viewer": FileViewer, 14 | }, 15 | props: { 16 | ids: { 17 | type: Array, 18 | default: () => [], 19 | }, 20 | officeViewerServiceUrl: String, 21 | }, 22 | setup(props) { 23 | const { state, dispatch } = useStore(); 24 | 25 | const { getFileUrl } = useHelpers(); 26 | 27 | const fileViewer = ref(null); 28 | 29 | const files = computed(() => { 30 | return state.items.data.files && props.ids 31 | ? state.items.data.files[props.ids.join(",")] || [] 32 | : []; 33 | }); 34 | 35 | watch( 36 | () => props.ids, 37 | async (newVal) => { 38 | if (newVal && newVal.length) { 39 | await dispatch("items/getItems", { 40 | resource: "files", 41 | api: publicFilesApi, 42 | ids: newVal, 43 | }); 44 | } else { 45 | files.value = []; 46 | } 47 | }, 48 | { immediate: true, deep: true } 49 | ); 50 | 51 | return { 52 | fileViewer, 53 | files, 54 | getFileUrl, 55 | }; 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-viewer/script.js: -------------------------------------------------------------------------------- 1 | import { ElMessage } from "element-plus"; 2 | import { useConsts } from "@/composables/use-consts"; 3 | import { reactive, ref } from "vue"; 4 | import OfficeViewer from "./components/office-viewer/index.vue"; 5 | 6 | export default { 7 | components: { 8 | "c-office-viewer": OfficeViewer, 9 | }, 10 | props: { 11 | urls: { 12 | type: Array, 13 | default: () => [], 14 | }, 15 | officeViewerServiceUrl: String, 16 | }, 17 | setup() { 18 | const officeViewer = ref(null); 19 | 20 | const cImageViewer = reactive({ 21 | visible: false, 22 | index: -1, 23 | }); 24 | 25 | const preview = (file, index) => { 26 | switch (true) { 27 | case ["jpg", "jpeg", "png", "gif"].includes(file.ext): 28 | previewImage(index); 29 | break; 30 | 31 | case ["ppt", "pptx", "doc", "docx", "xls", "xlsx"].includes(file.ext): 32 | previewOffice(file); 33 | break; 34 | 35 | default: 36 | ElMessage.error("该文件类型暂不支持预览"); 37 | break; 38 | } 39 | }; 40 | 41 | const previewImage = (index) => { 42 | cImageViewer.index = index; 43 | cImageViewer.visible = true; 44 | }; 45 | 46 | const previewOffice = ({ dir, date, uuid, ext }) => { 47 | const url = `${useConsts().StaticUrl}/${ 48 | dir ? `${dir}/` : "" 49 | }${date}/${uuid}.${ext}`; 50 | 51 | officeViewer.value.show({ src: url }); 52 | }; 53 | 54 | return { 55 | officeViewer, 56 | cImageViewer, 57 | preview, 58 | }; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /src/views/ads/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { adsApi } from "@/apis/admin/ads"; 4 | import { useEnums } from "element-plus-admin/composables/use-enums"; 5 | import { useValidators } from "vue-validation"; 6 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 7 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 8 | import { filesApi } from "@/apis/admin/files"; 9 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 10 | 11 | export default { 12 | emits: ["ok"], 13 | setup(props, context) { 14 | const formRef = ref(null); 15 | 16 | const { deepCopy } = useHelpers(); 17 | 18 | const { isRequired } = useValidators(); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const cDialog = reactive({ 23 | visible: false, 24 | }); 25 | 26 | const initialModel = {}; 27 | 28 | const cForm = reactive({ 29 | id: 0, 30 | model: deepCopy(initialModel), 31 | rules: { 32 | imageFileId: [isRequired({ message: "请选择广告图片" })], 33 | }, 34 | }); 35 | 36 | const { show, validateField, submit } = useFormDialog({ 37 | api: adsApi, 38 | cDialog, 39 | cForm, 40 | formRef, 41 | initialModel, 42 | onOk() { 43 | context.emit("ok"); 44 | }, 45 | }); 46 | 47 | return { 48 | tencentCloudCosApi, 49 | filesApi, 50 | UploadTo, 51 | enums, 52 | formRef, 53 | cDialog, 54 | cForm, 55 | show, 56 | validateField, 57 | submit, 58 | }; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /npm/element-plus-admin/composables/use-form-dialog.js: -------------------------------------------------------------------------------- 1 | import { watch } from "vue"; 2 | import { deepCopy } from "jt-helpers"; 3 | import { ElMessage } from "element-plus"; 4 | 5 | export const useFormDialog = ({ 6 | api, 7 | cDialog, 8 | cForm, 9 | formRef, 10 | initialModel, 11 | onSubmit, 12 | onOk, 13 | } = {}) => { 14 | watch( 15 | () => cDialog.visible, 16 | (newVal) => { 17 | if (!newVal) { 18 | cForm.model = deepCopy(initialModel); 19 | formRef.value && formRef.value.resetFields(); 20 | } 21 | } 22 | ); 23 | 24 | const show = ({ id, ...rest } = {}) => { 25 | cForm.id = id || 0; 26 | cForm.model = { ...initialModel, ...rest }; 27 | 28 | cDialog.visible = true; 29 | formRef.value && formRef.value.resetFields(); 30 | }; 31 | 32 | const validateField = (field) => { 33 | formRef.value.validateField(field); 34 | }; 35 | 36 | const validate = () => { 37 | return new Promise((resolve, reject) => { 38 | formRef.value.validate(async (valid) => { 39 | if (valid) { 40 | resolve(cForm); 41 | } else { 42 | reject(); 43 | } 44 | }); 45 | }); 46 | }; 47 | 48 | const submit = async () => { 49 | const { id, model } = await validate(); 50 | 51 | if (onSubmit) { 52 | await onSubmit({ id, model }); 53 | } else { 54 | await api[id ? "put" : "post"]({ id, body: model }); 55 | ElMessage.success(id ? "修改成功" : "新增成功"); 56 | } 57 | 58 | cDialog.visible = false; 59 | onOk && onOk(); 60 | }; 61 | 62 | return { 63 | show, 64 | validate, 65 | validateField, 66 | submit, 67 | }; 68 | }; 69 | -------------------------------------------------------------------------------- /src/views/jobs/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { jobsApi } from "@/apis/admin/jobs"; 4 | import { useEnums } from "element-plus-admin/composables/use-enums"; 5 | import { useValidators } from "vue-validation"; 6 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 7 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 8 | import { filesApi } from "@/apis/admin/files"; 9 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 10 | 11 | export default { 12 | emits: ["ok"], 13 | setup(props, context) { 14 | const formRef = ref(null); 15 | 16 | const { deepCopy } = useHelpers(); 17 | 18 | const { isRequired } = useValidators(); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const cDialog = reactive({ 23 | visible: false, 24 | }); 25 | 26 | const initialModel = {}; 27 | 28 | const cForm = reactive({ 29 | id: 0, 30 | model: deepCopy(initialModel), 31 | rules: { 32 | imageFileId: [isRequired({ message: "请选择广告图片" })], 33 | }, 34 | }); 35 | 36 | const { show, validateField, submit } = useFormDialog({ 37 | api: jobsApi, 38 | cDialog, 39 | cForm, 40 | formRef, 41 | initialModel, 42 | onOk() { 43 | context.emit("ok"); 44 | }, 45 | }); 46 | 47 | return { 48 | tencentCloudCosApi, 49 | filesApi, 50 | UploadTo, 51 | enums, 52 | formRef, 53 | cDialog, 54 | cForm, 55 | show, 56 | validateField, 57 | submit, 58 | }; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /src/assets/styles/global/components/iconfont-20230706.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "c-iconfont"; /* Project id 2745224 */ 3 | src: url("//at.alicdn.com/t/font_2745224_q7cc1l2clf.woff2?t=1648688085392") 4 | format("woff2"), 5 | url("//at.alicdn.com/t/font_2745224_q7cc1l2clf.woff?t=1648688085392") 6 | format("woff"), 7 | url("//at.alicdn.com/t/font_2745224_q7cc1l2clf.ttf?t=1648688085392") 8 | format("truetype"); 9 | } 10 | 11 | .c-iconfont { 12 | font-family: "c-iconfont" !important; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .c-iconfont--home-filled:before { 19 | content: "\e8c6"; 20 | } 21 | 22 | .c-iconfont--app-filled:before { 23 | content: "\e6d4"; 24 | } 25 | 26 | .c-iconfont--product-filled:before { 27 | content: "\e888"; 28 | } 29 | 30 | .c-iconfont--ad-filled:before { 31 | content: "\e600"; 32 | } 33 | 34 | .c-iconfont--order-filled:before { 35 | content: "\e616"; 36 | } 37 | 38 | .c-iconfont--camera-filled:before { 39 | content: "\e60d"; 40 | } 41 | 42 | .c-iconfont--upload-filled:before { 43 | content: "\e613"; 44 | } 45 | 46 | .c-iconfont--edit-filled:before { 47 | content: "\e607"; 48 | } 49 | 50 | .c-iconfont--more-drop-filled:before { 51 | content: "\e602"; 52 | } 53 | 54 | .c-iconfont--server-filled:before { 55 | content: "\e65b"; 56 | } 57 | 58 | .c-iconfont--setup-filled:before { 59 | content: "\e611"; 60 | } 61 | 62 | .c-iconfont--safe-filled:before { 63 | content: "\e63e"; 64 | } 65 | 66 | .c-iconfont--lock-filled:before { 67 | content: "\e62e"; 68 | } 69 | 70 | .c-iconfont--user-filled:before { 71 | content: "\e601"; 72 | } 73 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/file-list/script.js: -------------------------------------------------------------------------------- 1 | import { ref, watch } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import FileViewer from "../file-viewer/index.vue"; 4 | import { publicFilesApi } from "../../apis/public/files"; 5 | 6 | export default { 7 | components: { 8 | "c-file-viewer": FileViewer, 9 | }, 10 | props: { 11 | ids: { 12 | type: Array, 13 | default: () => [], 14 | }, 15 | className: { 16 | type: String, 17 | default: "", 18 | }, 19 | canPreview: { 20 | type: Boolean, 21 | default: true, 22 | }, 23 | officeViewerServiceUrl: String, 24 | }, 25 | setup(props) { 26 | const { getFileUrl } = useHelpers(); 27 | 28 | const fileViewer = ref(null); 29 | 30 | const files = ref([]); 31 | 32 | watch( 33 | () => props.ids, 34 | async (newVal) => { 35 | if (newVal && newVal.length) { 36 | const { items } = await publicFilesApi.post({ 37 | action: "getAllByIds", 38 | body: { ids: newVal }, 39 | }); 40 | 41 | files.value = items.map(({ id, name, ext }) => ({ 42 | id, 43 | name, 44 | ext, 45 | url: getFileUrl({ id }), 46 | })); 47 | } else { 48 | files.value = []; 49 | } 50 | }, 51 | { immediate: true, deep: true } 52 | ); 53 | 54 | const preview = (file, index) => { 55 | if (!props.canPreview) return; 56 | fileViewer.value.preview(file, index); 57 | }; 58 | 59 | return { 60 | fileViewer, 61 | files, 62 | getFileUrl, 63 | preview, 64 | }; 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /src/views/login/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/sidebar/script.js: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router"; 3 | 4 | export default { 5 | props: { 6 | showHomeMenu: { 7 | type: Boolean, 8 | default: false, 9 | }, 10 | menus: { 11 | type: Array, 12 | default: () => [], 13 | }, 14 | }, 15 | setup(props) { 16 | const router = useRouter(); 17 | const route = useRoute(); 18 | 19 | const getActiveKey = (path) => { 20 | let key = "-1"; 21 | 22 | props.menus.forEach((item1, index1) => { 23 | item1.children.forEach((item2, index2) => { 24 | const routePaths = (path || route.path).split("/"); 25 | const itemPaths = item2.path.split("/"); 26 | 27 | if ( 28 | routePaths[1] === itemPaths[1] && 29 | routePaths[2] === itemPaths[2] 30 | ) { 31 | key = `${index1}-${index2}`; 32 | } 33 | }); 34 | }); 35 | 36 | return key; 37 | }; 38 | 39 | const activeKey = ref(getActiveKey()); 40 | 41 | onBeforeRouteUpdate((to, from, next) => { 42 | activeKey.value = getActiveKey(to.path); 43 | next(); 44 | }); 45 | 46 | const onSelect = async (key) => { 47 | if (key === "-1") { 48 | await router.push("/"); 49 | } else { 50 | const indexes = key.split("-"); 51 | const index1 = indexes[0]; 52 | const index2 = indexes[1]; 53 | 54 | const { path } = props.menus[index1].children[index2]; 55 | 56 | await router.push(path); 57 | } 58 | }; 59 | 60 | return { 61 | router, 62 | activeKey, 63 | onSelect, 64 | }; 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /src/views/categories/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { useEnums } from "element-plus-admin/composables/use-enums"; 4 | import { useValidators } from "vue-validation"; 5 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 6 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 7 | import { filesApi } from "@/apis/admin/files"; 8 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 9 | import { categoriesApi } from "@/apis/admin/categories"; 10 | 11 | export default { 12 | emits: ["ok"], 13 | setup(props, context) { 14 | const formRef = ref(null); 15 | 16 | const { deepCopy } = useHelpers(); 17 | 18 | const { isRequired } = useValidators(); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const cDialog = reactive({ 23 | visible: false, 24 | }); 25 | 26 | const initialModel = {}; 27 | 28 | const cForm = reactive({ 29 | id: 0, 30 | model: deepCopy(initialModel), 31 | rules: { 32 | name: [isRequired({ label: "商品分类名称" })], 33 | iconFileId: [isRequired({ message: "请选择商品分类图标" })], 34 | }, 35 | }); 36 | 37 | const { show, validateField, submit } = useFormDialog({ 38 | api: categoriesApi, 39 | cDialog, 40 | cForm, 41 | formRef, 42 | initialModel, 43 | onOk() { 44 | context.emit("ok"); 45 | }, 46 | }); 47 | 48 | return { 49 | tencentCloudCosApi, 50 | filesApi, 51 | UploadTo, 52 | enums, 53 | formRef, 54 | cDialog, 55 | cForm, 56 | show, 57 | validateField, 58 | submit, 59 | }; 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /src/views/researches/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/users/list/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "serve": "vue-cli-service serve", 6 | "build": "vue-cli-service build", 7 | "build:test": "vue-cli-service build --mode test", 8 | "lint": "vue-cli-service lint", 9 | "format": "prettier --write \"src/**/*.{vue,js}\"" 10 | }, 11 | "dependencies": { 12 | "@element-plus/icons-vue": "^2.1.0", 13 | "element-plus": "^2.3.7", 14 | "element-plus-admin": "^1.2.17", 15 | "jt-helpers": "^1.0.12", 16 | "jt-storage": "^0.0.2", 17 | "jt-time": "^0.0.10", 18 | "pinia": "^2.1.4", 19 | "pinia-plugin-persistedstate": "^3.1.0", 20 | "sass-utils": "^1.1.12", 21 | "vue": "^3.3.4", 22 | "vue-router": "^4.2.2", 23 | "vue-validation": "^1.0.9", 24 | "vuex": "^4.1.0", 25 | "vuex-persistedstate": "^4.1.0" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.22.5", 29 | "@babel/eslint-parser": "^7.22.5", 30 | "@vue/cli-plugin-babel": "^5.0.8", 31 | "@vue/cli-plugin-eslint": "^5.0.8", 32 | "@vue/cli-service": "^5.0.8", 33 | "eslint": "^8.43.0", 34 | "eslint-config-prettier": "^8.8.0", 35 | "eslint-plugin-prettier": "^4.2.1", 36 | "eslint-plugin-vue": "^9.15.0", 37 | "prettier": "^2.8.8", 38 | "sass": "^1.63.6", 39 | "sass-loader": "^13.3.2" 40 | }, 41 | "eslintConfig": { 42 | "root": true, 43 | "env": { 44 | "node": true 45 | }, 46 | "extends": [ 47 | "plugin:vue/vue3-essential", 48 | "eslint:recommended", 49 | "plugin:prettier/recommended" 50 | ], 51 | "parserOptions": { 52 | "parser": "@babel/eslint-parser" 53 | }, 54 | "rules": { 55 | "vue/multi-word-component-names": 0 56 | } 57 | }, 58 | "browserslist": [ 59 | "> 1%", 60 | "last 2 versions", 61 | "not dead", 62 | "not ie 11" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/views/products/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { useEnums } from "element-plus-admin/composables/use-enums"; 4 | import { useValidators } from "vue-validation"; 5 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 6 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 7 | import { filesApi } from "@/apis/admin/files"; 8 | import { categoriesApi } from "@/apis/admin/categories"; 9 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 10 | import { productsApi } from "@/apis/admin/products"; 11 | 12 | export default { 13 | emits: ["ok"], 14 | setup(props, context) { 15 | const formRef = ref(null); 16 | 17 | const { deepCopy } = useHelpers(); 18 | 19 | const { isRequired } = useValidators(); 20 | 21 | const { enums } = useEnums(); 22 | 23 | const cDialog = reactive({ 24 | visible: false, 25 | }); 26 | 27 | const initialModel = {}; 28 | 29 | const cForm = reactive({ 30 | id: 0, 31 | model: deepCopy(initialModel), 32 | rules: { 33 | categoryId: [isRequired({ label: "商品分类" })], 34 | name: [isRequired({ label: "商品名称" })], 35 | imageFileIds: [isRequired({ message: "请选择商品图片" })], 36 | }, 37 | }); 38 | 39 | const { show, validateField, submit } = useFormDialog({ 40 | api: productsApi, 41 | cDialog, 42 | cForm, 43 | formRef, 44 | initialModel, 45 | onOk() { 46 | context.emit("ok"); 47 | }, 48 | }); 49 | 50 | return { 51 | tencentCloudCosApi, 52 | filesApi, 53 | categoriesApi, 54 | UploadTo, 55 | enums, 56 | formRef, 57 | cDialog, 58 | cForm, 59 | show, 60 | validateField, 61 | submit, 62 | }; 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /src/views/app-upgrades/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { useEnums } from "element-plus-admin/composables/use-enums"; 4 | import { useValidators } from "vue-validation"; 5 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 6 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 7 | import { filesApi } from "@/apis/admin/files"; 8 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 9 | import { appUpgradesApi } from "@/apis/admin/app-upgrades"; 10 | 11 | export default { 12 | emits: ["ok"], 13 | setup(props, context) { 14 | const formRef = ref(null); 15 | 16 | const { deepCopy } = useHelpers(); 17 | 18 | const { isRequired } = useValidators(); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const cDialog = reactive({ 23 | visible: false, 24 | }); 25 | 26 | const initialModel = { 27 | status: 1, 28 | }; 29 | 30 | const cForm = reactive({ 31 | id: 0, 32 | model: deepCopy(initialModel), 33 | rules: { 34 | platform: [isRequired({ label: "平台" })], 35 | versionName: [isRequired({ label: "版本名称" })], 36 | versionCode: [isRequired({ label: "版本号" })], 37 | log: [isRequired({ label: "更新日志" })], 38 | }, 39 | }); 40 | 41 | const { show, validateField, submit } = useFormDialog({ 42 | api: appUpgradesApi, 43 | cDialog, 44 | cForm, 45 | formRef, 46 | initialModel, 47 | onOk() { 48 | context.emit("ok"); 49 | }, 50 | }); 51 | 52 | return { 53 | tencentCloudCosApi, 54 | filesApi, 55 | UploadTo, 56 | enums, 57 | formRef, 58 | cDialog, 59 | cForm, 60 | show, 61 | validateField, 62 | submit, 63 | }; 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /src/views/articles/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/views/researches/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { researchesApi } from "@/apis/admin/researches"; 4 | import { ElMessage } from "element-plus"; 5 | import { useValidators } from "vue-validation"; 6 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 7 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 8 | import { filesApi } from "@/apis/admin/files"; 9 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 10 | 11 | export default { 12 | props: { 13 | path: String, 14 | }, 15 | emits: ["ok"], 16 | setup(props, context) { 17 | const formRef = ref(null); 18 | 19 | const { deepCopy } = useHelpers(); 20 | 21 | const { isRequired } = useValidators(); 22 | 23 | const cDialog = reactive({ 24 | visible: false, 25 | }); 26 | 27 | const initialModel = {}; 28 | 29 | const cForm = reactive({ 30 | id: 0, 31 | model: deepCopy(initialModel), 32 | rules: { 33 | title: [isRequired({ label: "标题" })], 34 | content: [isRequired({ label: "内容" })], 35 | }, 36 | }); 37 | 38 | const { show, validate, validateField } = useFormDialog({ 39 | cDialog, 40 | cForm, 41 | formRef, 42 | initialModel, 43 | }); 44 | 45 | const submit = async () => { 46 | const { id, model } = await validate(); 47 | 48 | await researchesApi[id ? "put" : "post"]({ 49 | id, 50 | body: { ...model, path: props.path }, 51 | query: { 52 | where: { path: props.path }, 53 | }, 54 | }); 55 | ElMessage.success(id ? "修改成功" : "新增成功"); 56 | context.emit("ok"); 57 | cDialog.visible = false; 58 | }; 59 | 60 | return { 61 | tencentCloudCosApi, 62 | filesApi, 63 | UploadTo, 64 | formRef, 65 | cDialog, 66 | cForm, 67 | show, 68 | validateField, 69 | submit, 70 | }; 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /src/views/researches/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { onMounted, ref } from "vue"; 4 | import { researchesApi } from "@/apis/admin/researches"; 5 | import Form from "./components/form/index.vue"; 6 | import { ElMessage } from "element-plus"; 7 | import { useRoute } from "vue-router"; 8 | 9 | export default { 10 | components: { 11 | "b-form": Form, 12 | }, 13 | setup() { 14 | const { params } = useRoute(); 15 | 16 | const formRef = ref(null); 17 | 18 | const currentPath = ref(""); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const { 23 | list, 24 | currentPage, 25 | cFilters, 26 | reRender, 27 | onPageChange, 28 | search, 29 | initialize, 30 | } = useList({ 31 | api: researchesApi, 32 | autoRender: false, 33 | filters: { 34 | model: { 35 | title: { $like: "" }, 36 | }, 37 | rules: {}, 38 | }, 39 | extraQuery: () => ({ 40 | order: [["order", "ASC"]], 41 | where: { path: currentPath.value, ...cFilters.model }, 42 | }), 43 | beforeRouteUpdate(to, from) { 44 | if (to.params.path !== from.params.path) { 45 | currentPath.value = to.params.path.replace("-", "/"); 46 | } 47 | }, 48 | }); 49 | 50 | onMounted(async () => { 51 | currentPath.value = params.path.replace("-", "/"); 52 | 53 | await initialize({ 54 | filters: { 55 | title: { $like: "" }, 56 | }, 57 | }); 58 | }); 59 | 60 | const del = async ({ id }) => { 61 | await researchesApi.delete({ id }); 62 | ElMessage.success("删除成功"); 63 | await reRender(); 64 | }; 65 | 66 | return { 67 | researchesApi, 68 | currentPath, 69 | formRef, 70 | enums, 71 | list, 72 | currentPage, 73 | cFilters, 74 | reRender, 75 | onPageChange, 76 | search, 77 | del, 78 | }; 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /src/views/team-members/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { onMounted, ref } from "vue"; 4 | import { teamMembersApi } from "@/apis/admin/team-members"; 5 | import Form from "./components/form/index.vue"; 6 | import { ElMessage } from "element-plus"; 7 | import { useRoute } from "vue-router"; 8 | 9 | export default { 10 | components: { 11 | "b-form": Form, 12 | }, 13 | setup() { 14 | const { params } = useRoute(); 15 | 16 | const form = ref(null); 17 | 18 | const currentPath = ref(""); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const { 23 | list, 24 | currentPage, 25 | cFilters, 26 | reRender, 27 | onPageChange, 28 | search, 29 | initialize, 30 | } = useList({ 31 | api: teamMembersApi, 32 | autoRender: false, 33 | filters: { 34 | model: { 35 | name: { $like: "" }, 36 | }, 37 | rules: {}, 38 | }, 39 | extraQuery: () => ({ 40 | order: [["order", "ASC"]], 41 | where: { path: currentPath.value, ...cFilters.model }, 42 | }), 43 | beforeRouteUpdate(to, from) { 44 | if (to.params.path !== from.params.path) { 45 | currentPath.value = to.params.path.replace("-", "/"); 46 | } 47 | }, 48 | }); 49 | 50 | onMounted(async () => { 51 | currentPath.value = params.path.replace("-", "/"); 52 | 53 | await initialize({ 54 | filters: { 55 | name: { $like: "" }, 56 | }, 57 | }); 58 | }); 59 | 60 | const del = async ({ id }) => { 61 | await teamMembersApi.delete({ id }); 62 | ElMessage.success("删除成功"); 63 | await reRender(); 64 | }; 65 | 66 | return { 67 | teamMembersApi, 68 | currentPath, 69 | form, 70 | enums, 71 | list, 72 | currentPage, 73 | cFilters, 74 | reRender, 75 | onPageChange, 76 | search, 77 | del, 78 | }; 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /src/views/ads/list/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/views/team-members/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { teamMembersApi } from "@/apis/admin/team-members"; 4 | import { ElMessage } from "element-plus"; 5 | import { useEnums } from "element-plus-admin/composables/use-enums"; 6 | import { useValidators } from "vue-validation"; 7 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 8 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 9 | import { filesApi } from "@/apis/admin/files"; 10 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 11 | 12 | export default { 13 | props: { 14 | path: String, 15 | }, 16 | emits: ["ok"], 17 | setup(props, context) { 18 | const formRef = ref(null); 19 | 20 | const { deepCopy } = useHelpers(); 21 | 22 | const { isRequired } = useValidators(); 23 | 24 | const { enums } = useEnums(); 25 | 26 | const cDialog = reactive({ 27 | visible: false, 28 | }); 29 | 30 | const initialModel = {}; 31 | 32 | const cForm = reactive({ 33 | id: 0, 34 | model: deepCopy(initialModel), 35 | rules: { 36 | name: [isRequired({ label: "姓名" })], 37 | }, 38 | }); 39 | 40 | const { show, validate, validateField } = useFormDialog({ 41 | cDialog, 42 | cForm, 43 | formRef, 44 | initialModel, 45 | }); 46 | 47 | const submit = async () => { 48 | const { id, model } = await validate(); 49 | 50 | await teamMembersApi[id ? "put" : "post"]({ 51 | id, 52 | body: { ...model, path: props.path }, 53 | query: { 54 | where: { path: props.path }, 55 | }, 56 | }); 57 | ElMessage.success(id ? "修改成功" : "新增成功"); 58 | context.emit("ok"); 59 | cDialog.visible = false; 60 | }; 61 | 62 | return { 63 | tencentCloudCosApi, 64 | filesApi, 65 | UploadTo, 66 | enums, 67 | formRef, 68 | cDialog, 69 | cForm, 70 | show, 71 | validateField, 72 | submit, 73 | }; 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /src/views/categories/list/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/editor/script.js: -------------------------------------------------------------------------------- 1 | import "@wangeditor/editor/dist/css/style.css"; 2 | import { onBeforeUnmount, ref, shallowRef, watch } from "vue"; 3 | import { Editor, Toolbar } from "@wangeditor/editor-for-vue"; 4 | import { useConsts } from "@/composables/use-consts"; 5 | import { useAuth } from "../../composables/use-auth"; 6 | import { useUpload } from "./composables/use-upload"; 7 | 8 | const { ApiUrl } = useConsts(); 9 | const { getHeaders } = useAuth(); 10 | 11 | export default { 12 | components: { Editor, Toolbar }, 13 | props: { 14 | value: { 15 | type: String, 16 | default: "", 17 | }, 18 | style: { 19 | type: String, 20 | default: "height: 500px", 21 | }, 22 | uploadAction: { 23 | type: String, 24 | default: `${ApiUrl}/admin/files/actions/upload`, 25 | }, 26 | uploadHeaders: { 27 | type: Object, 28 | default: () => getHeaders(), 29 | }, 30 | cosConfig: { 31 | type: Object, 32 | default: () => null, 33 | }, 34 | }, 35 | emits: ["update:value"], 36 | setup(props, context) { 37 | const upload = useUpload({ props }); 38 | 39 | const editorRef = shallowRef(); 40 | 41 | const valueHtml = ref(props.value); 42 | 43 | watch( 44 | () => props.value, 45 | (newVal) => { 46 | valueHtml.value = newVal; 47 | } 48 | ); 49 | 50 | watch( 51 | () => valueHtml.value, 52 | (newVal) => { 53 | context.emit("update:value", newVal); 54 | } 55 | ); 56 | 57 | const toolbarConfig = {}; 58 | 59 | const editorConfig = { 60 | MENU_CONF: {}, 61 | placeholder: "请输入内容...", 62 | }; 63 | 64 | upload.configEditor({ editorConfig }); 65 | 66 | onBeforeUnmount(() => { 67 | const editor = editorRef.value; 68 | if (editor == null) return; 69 | editor.destroy(); 70 | }); 71 | 72 | const onCreated = (editor) => { 73 | editorRef.value = editor; 74 | }; 75 | 76 | return { 77 | editorRef, 78 | valueHtml, 79 | toolbarConfig, 80 | editorConfig, 81 | onCreated, 82 | }; 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /src/views/contents/script.js: -------------------------------------------------------------------------------- 1 | import { onMounted, reactive, ref } from "vue"; 2 | import { useValidators } from "vue-validation"; 3 | import { onBeforeRouteUpdate, useRoute } from "vue-router"; 4 | import { contentsApi } from "@/apis/admin/contents"; 5 | import { ElMessage } from "element-plus"; 6 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 7 | import { filesApi } from "@/apis/admin/files"; 8 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 9 | 10 | export default { 11 | setup() { 12 | const { params } = useRoute(); 13 | 14 | const form = ref(null); 15 | 16 | const { isRequired } = useValidators(); 17 | 18 | const currentPath = ref(""); 19 | 20 | const cForm = reactive({ 21 | model: { 22 | content: "", 23 | }, 24 | rules: { 25 | content: [isRequired({ message: "内容不能为空" })], 26 | }, 27 | }); 28 | 29 | onMounted(async () => { 30 | await render(params.path); 31 | }); 32 | 33 | onBeforeRouteUpdate(async (to, from, next) => { 34 | cForm.model.content = ""; 35 | await render(to.params.path); 36 | next(); 37 | }); 38 | 39 | const render = async (path) => { 40 | currentPath.value = path.replace("-", "/"); 41 | 42 | const { content } = await contentsApi.post({ 43 | action: "findByPath", 44 | body: { path: currentPath.value }, 45 | }); 46 | 47 | cForm.model.content = content; 48 | console.log(cForm.model.content, content, "--"); 49 | }; 50 | 51 | const submit = async () => { 52 | form.value.validate(async (valid) => { 53 | if (valid) { 54 | await contentsApi.post({ 55 | action: "updateByPath", 56 | body: { 57 | path: currentPath.value, 58 | content: cForm.model.content, 59 | }, 60 | }); 61 | 62 | ElMessage.success("保存成功"); 63 | } 64 | }); 65 | }; 66 | 67 | return { 68 | tencentCloudCosApi, 69 | filesApi, 70 | UploadTo, 71 | form, 72 | cForm, 73 | currentPath, 74 | submit, 75 | }; 76 | }, 77 | }; 78 | -------------------------------------------------------------------------------- /src/views/orders/list/components/detail/index.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/views/articles/list/script.js: -------------------------------------------------------------------------------- 1 | import { useList } from "element-plus-admin/components/list/composables/use-list"; 2 | import { useEnums } from "element-plus-admin/composables/use-enums"; 3 | import { onMounted, ref } from "vue"; 4 | import { articlesApi } from "@/apis/admin/articles"; 5 | import Form from "./components/form/index.vue"; 6 | import { ElMessage } from "element-plus"; 7 | import { useRoute } from "vue-router"; 8 | 9 | export default { 10 | components: { 11 | "b-form": Form, 12 | }, 13 | setup() { 14 | const { params } = useRoute(); 15 | 16 | const formRef = ref(null); 17 | 18 | const currentPath = ref(""); 19 | 20 | const { enums } = useEnums(); 21 | 22 | const { 23 | list, 24 | currentPage, 25 | cFilters, 26 | reRender, 27 | onPageChange, 28 | search, 29 | initialize, 30 | } = useList({ 31 | api: articlesApi, 32 | autoRender: false, 33 | filters: { 34 | model: { 35 | title: { $like: "" }, 36 | }, 37 | rules: {}, 38 | }, 39 | extraQuery: () => ({ 40 | order: [["order", "DESC"]], 41 | where: { path: currentPath.value, ...cFilters.model }, 42 | }), 43 | beforeRouteUpdate(to, from) { 44 | if (to.params.path !== from.params.path) { 45 | currentPath.value = to.params.path.replace("-", "/"); 46 | } 47 | }, 48 | }); 49 | 50 | onMounted(async () => { 51 | currentPath.value = params.path.replace("-", "/"); 52 | 53 | await initialize({ 54 | filters: { 55 | title: { $like: "" }, 56 | }, 57 | }); 58 | }); 59 | 60 | const del = async ({ id }) => { 61 | await articlesApi.delete({ id }); 62 | ElMessage.success("删除成功"); 63 | await reRender(); 64 | }; 65 | 66 | const getStatus = (row) => 67 | row.draft && (!row.content || row.content === "


") 68 | ? "草稿" 69 | : "已发布"; 70 | 71 | return { 72 | articlesApi, 73 | currentPath, 74 | formRef, 75 | enums, 76 | list, 77 | currentPage, 78 | cFilters, 79 | reRender, 80 | onPageChange, 81 | search, 82 | del, 83 | getStatus, 84 | }; 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /src/views/jobs/list/index.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/views/articles/list/components/form/script.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import { useHelpers } from "@/composables/use-helpers"; 3 | import { articlesApi } from "@/apis/admin/articles"; 4 | import { ElMessage } from "element-plus"; 5 | import { useValidators } from "vue-validation"; 6 | import { useFormDialog } from "element-plus-admin/composables/use-form-dialog"; 7 | import { tencentCloudCosApi } from "@/apis/admin/tencent-cloud-cos"; 8 | import { filesApi } from "@/apis/admin/files"; 9 | import { UploadTo } from "element-plus-admin/enums/upload-to"; 10 | 11 | export default { 12 | props: { 13 | path: String, 14 | }, 15 | emits: ["ok"], 16 | setup(props, context) { 17 | const formRef = ref(null); 18 | 19 | const { deepCopy } = useHelpers(); 20 | 21 | const { isRequired } = useValidators(); 22 | 23 | const cDialog = reactive({ 24 | visible: false, 25 | }); 26 | 27 | const initialModel = {}; 28 | 29 | const cForm = reactive({ 30 | id: 0, 31 | model: deepCopy(initialModel), 32 | rules: { 33 | title: [isRequired({ label: "标题" })], 34 | content: [isRequired({ label: "内容" })], 35 | }, 36 | }); 37 | 38 | const { show, validate, validateField } = useFormDialog({ 39 | cDialog, 40 | cForm, 41 | formRef, 42 | initialModel, 43 | }); 44 | 45 | const getDraft = () => { 46 | cForm.model.content = cForm.model.draft; 47 | }; 48 | 49 | const submit = async ({ draft = false } = {}) => { 50 | const { id, model } = await validate(); 51 | 52 | const postData = { ...model, path: props.path }; 53 | 54 | if (draft) { 55 | postData.draft = model.content; 56 | delete postData.content; 57 | } 58 | 59 | await articlesApi[id ? "put" : "post"]({ 60 | id, 61 | body: postData, 62 | query: { 63 | where: { path: props.path }, 64 | }, 65 | }); 66 | ElMessage.success(id ? "修改成功" : "新增成功"); 67 | context.emit("ok"); 68 | cDialog.visible = false; 69 | }; 70 | 71 | return { 72 | tencentCloudCosApi, 73 | filesApi, 74 | UploadTo, 75 | formRef, 76 | cDialog, 77 | cForm, 78 | show, 79 | validateField, 80 | getDraft, 81 | submit, 82 | }; 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /src/views/researches/list/index.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/views/articles/list/index.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/views/jobs/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/products/list/index.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/team-members/list/index.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/app-upgrades/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/views/products/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/views/orders/list/index.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/views/team-members/list/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/layout/main/script.js: -------------------------------------------------------------------------------- 1 | import { onMounted, ref } from "vue"; 2 | import { onBeforeRouteUpdate, useRoute } from "vue-router"; 3 | 4 | export default { 5 | props: { 6 | menus: { 7 | type: Array, 8 | default: () => [], 9 | }, 10 | }, 11 | setup(props) { 12 | const { query } = useRoute(); 13 | 14 | const menus = ref([{}, {}, {}, {}]); 15 | 16 | const renderMenus = (path) => { 17 | const routePaths = path.substr(1).split("/"); 18 | 19 | menus.value = [{}, {}, {}, {}]; 20 | 21 | props.menus.forEach((item1) => { 22 | item1.children.forEach((item2) => { 23 | const itemPaths = item2.path.substr(1).split("/"); 24 | 25 | if ( 26 | routePaths[0] === itemPaths[0] && 27 | routePaths[1] === itemPaths[1] 28 | ) { 29 | menus.value[0] = item1; 30 | menus.value[1] = item2; 31 | 32 | if (item2.children) { 33 | item2.children.forEach((item3) => { 34 | const itemPaths = item3.path.substr(1).split("/"); 35 | 36 | if ( 37 | routePaths[2] === itemPaths[2] || 38 | (itemPaths[2] === ":id" && 39 | !isNaN(routePaths[2]) && 40 | itemPaths[3] === routePaths[3]) 41 | ) { 42 | menus.value[2] = { 43 | ...item3, 44 | route: { 45 | path: item3.path.replace(":id", routePaths[2]), 46 | query: { 47 | $menu1Title: query.$menu1Title, 48 | }, 49 | }, 50 | }; 51 | 52 | if (item3.children) { 53 | item3.children.forEach((item4) => { 54 | const itemPaths = item4.path.substr(1).split("/"); 55 | 56 | if ( 57 | routePaths[4] === itemPaths[4] || 58 | (itemPaths[4] === ":id" && 59 | !isNaN(routePaths[4]) && 60 | itemPaths[5] === routePaths[5]) 61 | ) { 62 | menus.value[3] = { 63 | ...item4, 64 | route: { 65 | path: item4.path 66 | .replace(":id", routePaths[2]) 67 | .replace(":id", routePaths[4]), 68 | query: { 69 | $menu1Title: query.$menu1Title, 70 | $menu2Title: query.$menu2Title, 71 | }, 72 | }, 73 | }; 74 | } 75 | }); 76 | } 77 | } 78 | }); 79 | } 80 | } 81 | }); 82 | }); 83 | }; 84 | 85 | onMounted(() => { 86 | renderMenus(useRoute().path); 87 | }); 88 | 89 | onBeforeRouteUpdate(async (to, from, next) => { 90 | renderMenus(to.path); 91 | next(); 92 | }); 93 | 94 | return { 95 | menus, 96 | }; 97 | }, 98 | }; 99 | -------------------------------------------------------------------------------- /src/views/app-upgrades/list/index.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/script.js: -------------------------------------------------------------------------------- 1 | import { useConsts } from "@/composables/use-consts"; 2 | import { useAuth } from "../../composables/use-auth"; 3 | import { deepCopy } from "jt-helpers"; 4 | import Files from "./files/index.vue"; 5 | import { onMounted, reactive } from "vue"; 6 | import { useCos } from "./composables/use-cos"; 7 | 8 | const { ApiUrl } = useConsts(); 9 | const { getHeaders } = useAuth(); 10 | 11 | export default { 12 | components: { 13 | "cc-files": Files, 14 | }, 15 | props: { 16 | action: { 17 | type: String, 18 | default: `${ApiUrl}/admin/files/actions/upload`, 19 | }, 20 | headers: { 21 | type: Object, 22 | default: () => getHeaders(), 23 | }, 24 | data: Object, 25 | multiple: { 26 | type: Boolean, 27 | default: false, 28 | }, 29 | placeholder: { 30 | type: String, 31 | default: "请选择文件", 32 | }, 33 | buttonSize: { 34 | type: String, 35 | default: "default", 36 | }, 37 | buttonClass: String, 38 | showUploaded: { 39 | type: Boolean, 40 | default: true, 41 | }, 42 | fileDir: { 43 | type: String, 44 | default: "", 45 | }, 46 | cosConfig: { 47 | type: Object, 48 | default: () => null, 49 | }, 50 | value: { 51 | type: [Number, Array], 52 | default: 0, 53 | }, 54 | tip: { 55 | type: String, 56 | default: "", 57 | }, 58 | officeViewerServiceUrl: String, 59 | }, 60 | emits: ["update:value", "change", "success", "error"], 61 | setup(props, context) { 62 | const cUpload = reactive({ 63 | progress: 0, 64 | }); 65 | 66 | const cos = useCos({ 67 | ...props.cosConfig, 68 | onProgress(progress) { 69 | cUpload.progress = progress; 70 | }, 71 | }); 72 | 73 | onMounted(async () => { 74 | if (props.cosConfig) { 75 | await cos.initialize(); 76 | } 77 | }); 78 | 79 | const update = (id) => { 80 | if (props.multiple) { 81 | const value = props.value ? [...props.value, id] : [id]; 82 | 83 | context.emit("update:value", value); 84 | context.emit("change", value); 85 | } else { 86 | context.emit("update:value", id); 87 | context.emit("change", id); 88 | } 89 | }; 90 | 91 | const beforeUpload = async (file) => { 92 | if (props.cosConfig) { 93 | update((await cos.upload(file, props.fileDir)).id); 94 | return Promise.reject(); 95 | } else { 96 | return Promise.resolve(); 97 | } 98 | }; 99 | 100 | const onSuccess = (res) => { 101 | context.emit("success", res.data); 102 | update(res.data.id); 103 | }; 104 | 105 | const onError = (err, file, fileList) => { 106 | context.emit("error", { err, file, fileList }); 107 | }; 108 | 109 | const onDelete = (index) => { 110 | if (props.multiple) { 111 | const value = deepCopy(props.value); 112 | 113 | value.splice(index, 1); 114 | 115 | context.emit("update:value", value); 116 | context.emit("change", value); 117 | } else { 118 | context.emit("update:value", undefined); 119 | context.emit("change", undefined); 120 | } 121 | }; 122 | 123 | return { 124 | cUpload, 125 | beforeUpload, 126 | onSuccess, 127 | onError, 128 | onDelete, 129 | }; 130 | }, 131 | }; 132 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/upload/composables/use-cos.js: -------------------------------------------------------------------------------- 1 | import * as AliCloudOss from "ali-oss"; 2 | import * as TencentCloudCos from "cos-js-sdk-v5"; 3 | import { UploadTo } from "../../../enums/upload-to"; 4 | 5 | export const useCos = ({ 6 | cosApi, 7 | filesApi, 8 | uploadTo, 9 | region, 10 | bucket, 11 | onProgress, 12 | }) => { 13 | let client = null; 14 | 15 | const initialize = async () => { 16 | switch (uploadTo) { 17 | case UploadTo.AliCloudOss: 18 | { 19 | const { 20 | Credentials: { AccessKeyId, AccessKeySecret, SecurityToken }, 21 | } = await cosApi.post({ 22 | action: "getStsCredential", 23 | body: { region, bucket }, 24 | }); 25 | 26 | client = new AliCloudOss({ 27 | region, 28 | bucket, 29 | accessKeyId: AccessKeyId, 30 | accessKeySecret: AccessKeySecret, 31 | stsToken: SecurityToken, 32 | }); 33 | } 34 | break; 35 | 36 | case UploadTo.TencentCloudOss: 37 | { 38 | const { 39 | credentials: { tmpSecretId, tmpSecretKey, sessionToken }, 40 | startTime, 41 | expiredTime, 42 | } = await cosApi.post({ 43 | action: "getStsCredential", 44 | body: { region, bucket }, 45 | }); 46 | 47 | client = new TencentCloudCos({ 48 | getAuthorization(options, callback) { 49 | callback({ 50 | TmpSecretId: tmpSecretId, 51 | TmpSecretKey: tmpSecretKey, 52 | SecurityToken: sessionToken, 53 | StartTime: startTime, 54 | ExpiredTime: expiredTime, 55 | }); 56 | }, 57 | }); 58 | } 59 | break; 60 | 61 | default: 62 | break; 63 | } 64 | }; 65 | 66 | const upload = async (file, fileDir) => { 67 | const { name, type, size } = file; 68 | const ext = name.split(".").pop(); 69 | 70 | const { id, date, uuid } = await filesApi.post({ 71 | action: "create", 72 | body: { dir: fileDir }, 73 | }); 74 | const filePath = `${fileDir ? fileDir + "/" : ""}${date}/${uuid}.${ext}`; 75 | 76 | switch (uploadTo) { 77 | case UploadTo.AliCloudOss: 78 | await client.multipartUpload(filePath, file, { 79 | progress(p) { 80 | onProgress && onProgress(+(p * 100).toFixed(0)); 81 | }, 82 | parallel: 4, 83 | partSize: 1024 * 1024, 84 | meta: { year: 2020, people: "test" }, 85 | mime: "text/plain", 86 | }); 87 | break; 88 | 89 | case UploadTo.TencentCloudOss: 90 | await new Promise((resolve, reject) => { 91 | client.putObject( 92 | { 93 | Bucket: bucket, 94 | Region: region, 95 | Key: filePath, 96 | Body: file, 97 | onProgress({ percent }) { 98 | onProgress && onProgress(+(percent * 100).toFixed(0)); 99 | }, 100 | }, 101 | (err, data) => { 102 | if (err) { 103 | reject(err); 104 | } else { 105 | resolve(data); 106 | } 107 | } 108 | ); 109 | }); 110 | break; 111 | 112 | default: 113 | break; 114 | } 115 | 116 | onProgress && onProgress(0); 117 | 118 | await filesApi.post({ 119 | action: "update", 120 | body: { date, uuid, name, type, ext, size }, 121 | }); 122 | 123 | return { id }; 124 | }; 125 | 126 | return { 127 | client, 128 | initialize, 129 | upload, 130 | }; 131 | }; 132 | -------------------------------------------------------------------------------- /npm/element-plus-admin/utils/create-api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import NProgress from "nprogress"; 3 | import "nprogress/nprogress.css"; 4 | import { ElMessage } from "element-plus"; 5 | import { debounce } from "throttle-debounce"; 6 | 7 | NProgress.configure({ showSpinner: false }); 8 | 9 | const startProgress = debounce(200, NProgress.start, { atBegin: true }); 10 | 11 | const doneProgress = debounce(200, NProgress.done, { atBegin: false }); 12 | 13 | const createRequest = ({ baseUrl, timeout = 5000, query, body }) => { 14 | const request = axios.create({ 15 | baseURL: baseUrl || process.env.VUE_APP_API_URL, 16 | timeout, 17 | }); 18 | 19 | request.interceptors.request.use( 20 | (config) => { 21 | const { method, params, data, showLoading } = config; 22 | 23 | showLoading && startProgress(); 24 | 25 | if (params) { 26 | if (params.where) { 27 | if (params.where._vts) { 28 | delete params.where._vts; 29 | } 30 | 31 | config.params.where = formatWhere({ 32 | ...(query().where || {}), 33 | ...params.where, 34 | }); 35 | } else { 36 | config.params.where = formatWhere(query().where || {}); 37 | } 38 | } 39 | 40 | if (data) { 41 | config.data = { ...body(), ...data }; 42 | } 43 | 44 | ["include", "order", "attributes"].forEach((key) => { 45 | if (params && params[key]) { 46 | config.params[key] = JSON.stringify(params[key]); 47 | } 48 | }); 49 | 50 | if (method === "get") { 51 | if (params) { 52 | config.params._ = new Date().getTime(); 53 | } else { 54 | config.params = { _: new Date().getTime() }; 55 | } 56 | } 57 | 58 | return config; 59 | }, 60 | (error) => Promise.reject(error) 61 | ); 62 | 63 | request.interceptors.response.use( 64 | (response) => { 65 | const { 66 | config, 67 | data: { data }, 68 | } = response; 69 | 70 | config.showLoading && doneProgress(); 71 | 72 | return data; 73 | }, 74 | (error) => { 75 | const { 76 | response: { config, status, data }, 77 | } = error; 78 | 79 | config.showLoading && doneProgress(); 80 | 81 | if (status === 401) { 82 | window.location.href = "/#/logout"; 83 | } else { 84 | if (config.showError) { 85 | if (data.error && data.error.message) { 86 | const { message } = data.error; 87 | 88 | if (typeof message === "string") { 89 | ElMessage.error(message); 90 | } else if (typeof message === "object") { 91 | ElMessage.error({ 92 | message: Object.values(message).join("
"), 93 | dangerouslyUseHTMLString: true, 94 | }); 95 | } 96 | } else { 97 | ElMessage.error("服务器内部错误"); 98 | } 99 | } 100 | } 101 | 102 | return Promise.reject(error); 103 | } 104 | ); 105 | 106 | return request; 107 | }; 108 | 109 | const formatWhere = (obj) => { 110 | const ret = {}; 111 | 112 | Object.keys(obj).forEach((attribute) => { 113 | ret[attribute] = {}; 114 | 115 | Object.keys(obj[attribute]).forEach((operator) => { 116 | if (operator.substring(0, 2) === "$$") { 117 | ret[attribute] = { 118 | [operator.replace("$$", "$")]: obj[attribute][operator], 119 | }; 120 | } else if ( 121 | obj[attribute][operator] === undefined || 122 | obj[attribute][operator] === "" || 123 | obj[attribute][operator] === null 124 | ) { 125 | delete ret[attribute]; 126 | } else if (operator === "$like") { 127 | ret[attribute][operator] = `%${obj[attribute][operator]}%`; 128 | } else { 129 | ret[attribute] = obj[attribute]; 130 | } 131 | }); 132 | }); 133 | 134 | return JSON.stringify(ret); 135 | }; 136 | 137 | export const createApi = ({ 138 | baseUrl, 139 | url, 140 | headers = () => ({}), 141 | query = () => ({}), 142 | body = () => ({}), 143 | }) => { 144 | const request = createRequest({ baseUrl, query, body }); 145 | 146 | return { 147 | config: { baseUrl, url, query, body }, 148 | 149 | get: ({ 150 | joinUrl = "", 151 | id, 152 | query = {}, 153 | timeout = 5000, 154 | showLoading = true, 155 | showError = true, 156 | } = {}) => 157 | request.get(`${url}${joinUrl}${id ? `/${id}` : ""}`, { 158 | headers: headers(), 159 | params: query, 160 | timeout, 161 | showLoading, 162 | showError, 163 | }), 164 | 165 | post: ({ 166 | joinUrl = "", 167 | action, 168 | body = {}, 169 | query = {}, 170 | timeout = 5000, 171 | showLoading = true, 172 | showError = true, 173 | } = {}) => 174 | request.post( 175 | action ? `${url}${joinUrl}/actions/${action}` : url + joinUrl, 176 | body, 177 | { 178 | headers: headers(), 179 | params: query, 180 | timeout, 181 | showLoading, 182 | showError, 183 | } 184 | ), 185 | 186 | put: ({ 187 | joinUrl = "", 188 | id, 189 | body = {}, 190 | query = {}, 191 | timeout = 5000, 192 | showLoading = true, 193 | showError = true, 194 | } = {}) => 195 | request.put(`${url}${joinUrl}/${id}`, body, { 196 | headers: headers(), 197 | params: query, 198 | timeout, 199 | showLoading, 200 | showError, 201 | }), 202 | 203 | delete: ({ 204 | joinUrl = "", 205 | id, 206 | query = {}, 207 | timeout = 5000, 208 | showLoading = true, 209 | showError = true, 210 | } = {}) => 211 | request.delete(`${url}${joinUrl}/${id}`, { 212 | headers: headers(), 213 | params: query, 214 | timeout, 215 | showLoading, 216 | showError, 217 | }), 218 | }; 219 | }; 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 关于 2 | 3 | #### 介绍 4 | 5 | 基于 Vue 3.0 + Element Plus 的后台前端解决方案,支持:开发调试、构建、代码规范校验等。 6 | 7 | #### 技术栈 8 | 9 | Vue、Webpack、ES6、Vue Router、Vuex、Sass、PostCSS 等。 10 | 11 | ## 命令 12 | 13 | ```bash 14 | # 安装依赖 15 | $ npm install 16 | 17 | # 开发调试 18 | $ npm run serve 19 | 20 | # 代码校验 21 | $ npm run lint 22 | 23 | # 构建 24 | $ npm run build 25 | 26 | # 从模板创建文件 27 | $ npm run newfile 28 | ``` 29 | 30 | ## 规范 31 | 32 | #### 项目结构 33 | 34 | ``` 35 | |- plop-templates 基本模板 36 | |- public 网站公共目录 37 | | |- favicon.ico 网站图标 38 | | |- index.html HTML 模板 39 | |- src 源码目录 40 | | |- assets 待编译的静态资源 41 | | |- images 图片 42 | | |- components 组件图片 43 | | |- styles 样式 44 | | |- global 全局样式 45 | | |- components 组件样式 46 | | |- iconfont.scss iconfont 47 | | |- reset CSS Reset 48 | | |- utils Sass 工具 49 | | |- variables Sass 变量 50 | | |- components 公共组件 51 | | |- composables 公用 composiable 函数 52 | | |- models 数据模型 53 | | |- router 路由配置 54 | | |- routes 各个视图的路由配置 55 | | |- store 状态管理 56 | | |- modules 状态管理(指定命名空间) 57 | | |- auth.js auth 状态管理 58 | | |- utils JS 工具 59 | | |- views 视图 60 | | |- home 首页 61 | | |- App.vue 页面入口 62 | | |- main.js 程序入口 63 | ``` 64 | 65 | #### 公共组件规范 66 | 67 | 公共组件放在 /src/components 下。 68 | 69 | ``` 70 | |- my-component my component 组件 71 | | |- index.vue my component 的入口 72 | | |- script.js my component 的脚本 73 | | |- style.scss my component 的样式 74 | | |- utils my component 的 JS 工具 75 | | |- components my component 的子组件 76 | | |- child-component my component 的子组件 child component 77 | ``` 78 | 79 | #### 视图规范 80 | 81 | 视图,也就是页面,放在 /src/views 下。规范和公共组件一致。 82 | 83 | ## 链接 84 | 85 | #### 文档 86 | 87 | - [Vue3 中文文档(官方)](https://v3.cn.vuejs.org/) 88 | - [Vue3 中文文档](https://vue3js.cn/docs/zh/) 89 | - [Vue3 组合式 API(官方)](https://composition-api.vuejs.org/zh/api.html) 90 | - [Vue3 组合式 API](https://vue3js.cn/vue-composition-api/) 91 | - [Vue3 组合式 API 高阶指南](https://vue3js.cn/docs/zh/guide/composition-api-introduction.html) 92 | - [Vue3 资源推荐](https://vue3js.cn/) 93 | - [Vue3 迁移指南](https://vue3js.cn/docs/zh/guide/migration/introduction.html) 94 | - [Vue3 API 参考](https://vue3js.cn/docs/zh/api/) 95 | - [Vue3 风格指南](https://vue3js.cn/docs/zh/style-guide/) 96 | 97 | #### 配套工具 98 | 99 | - [Vue CLI](https://cli.vuejs.org/migrating-from-v3/) 100 | - [Vue Router](https://vue3js.cn/router4/) 101 | - [Vuex](https://vue3js.cn/vuex/zh/) 102 | 103 | #### 视频 104 | 105 | - [米修在线 - Vue3 快速上手指南](https://www.bilibili.com/video/BV1HT4y137m3) 106 | - [李南江 - Vue3 正式版教程](https://www.bilibili.com/video/BV14k4y117LL) 107 | - [哈默聊前端 - Vue3](https://space.bilibili.com/492976859/video) 108 | - [大地 - Vue3 教程](https://www.bilibili.com/video/BV1zt411e7fp) 109 | - [Young 村长 - Composition API + 深度解读](https://www.bilibili.com/video/BV1my4y1m7sz) 110 | - [Young 村长 - Vue3 光速上手](https://www.bilibili.com/video/BV1Wh411X7Xp) 111 | - [Young 村长 - 项目打包、部署、CI/CD](https://www.bilibili.com/video/BV1Wh411X7Xp?p=30) 112 | 113 | #### 链接 114 | 115 | - [【Vue3 官方教程】🎄 万字笔记 | 同步导学视频 ](https://juejin.cn/post/6909247394904702984) 116 | - [vue3-study](https://github.com/su37josephxia/vue3-study) 117 | - [Vue-Mastery 学习笔记](https://www.yuque.com/nxtt7g/kompdt) 118 | - [@vue/composition-api - 用于提供组合式 API 的 Vue 2 插件](https://github.com/vuejs/composition-api/blob/master/README.zh-CN.md) 119 | - [@vue/composition-api 速成课](https://blog.csdn.net/frontend_frank/article/details/108786784) 120 | - [Vue Class Component v8 - The next Vue Class Component for Vue v3.](https://github.com/vuejs/vue-class-component/tree/next) 121 | - [Vue3 实战笔记](https://juejin.cn/post/6909632635665039367) 122 | - [快速使用 Vue3 最新的 15 个常用 API](https://juejin.cn/post/6897030228867022856) 123 | 124 | #### 开源 125 | 126 | - [View Shadcn UI](https://view-shadcn-ui.devlive.org/) 127 | - [element-plus-kit](https://github.com/mitjs/element-plus-kit) 128 | - [element-plus-x](https://github.com/SorrowX/element-plus-x) 129 | - [agile-admin](https://github.com/gmingchen/agile-admin) 130 | - [element-plus-theme](https://github.com/ele-admin/element-plus-theme) 131 | - [miitvip-vue-admin-manager](https://github.com/lirongtong/miitvip-vue-admin-manager) 132 | - [nuxt-shadcn-dashboard](https://github.com/dianprata/nuxt-shadcn-dashboard) 133 | - [M-Admin](https://github.com/sina-xys-felix/M-Admin) 134 | - [opentiny](https://opentiny.design/) 135 | - [art-design-pro](https://www.lingchen.kim/art-design-pro/docs/zh/) 136 | - [una-ui](https://github.com/una-ui/una-ui) 137 | - [sz-admin](https://github.com/feiyuchuixue/sz-admin) 138 | - [continew-admin](https://github.com/continew-org/continew-admin) 139 | - [nova-admin](https://github.com/chansee97/nova-admin) 140 | - [Snowy](https://github.com/xiaonuobase/Snowy) 141 | - [smart-admin](https://github.com/1024-lab/smart-admin) 142 | - [shadcn-admin](https://github.com/satnaing/shadcn-admin) 143 | - [primevue](https://github.com/primefaces/primevue) 144 | - [volt](https://volt.primevue.org/) 145 | - [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin) 146 | - [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) 147 | - [vue3-antdv-admin](https://github.com/buqiyuan/vue3-antdv-admin) 148 | - [vue-admin-better](https://github.com/chuzhixin/vue-admin-better) 149 | - [vue-element-ui-admin(Vue3+ScriptSetup)](https://github.com/xusenlin/vue-element-ui-admin) 150 | - [v3-admin-vite](https://github.com/un-pany/v3-admin-vite) 151 | - [vue-element-plus-admin](https://github.com/kailong321200875/vue-element-plus-admin) 152 | - [SnowAdmin](https://gitee.com/wang_fan_w/SnowAdmin) 153 | - [vue3-element-admin](https://github.com/youlaitech/vue3-element-admin) 154 | - [naive-ui-admin](https://github.com/jekip/naive-ui-admin) 155 | - [soybean-admin](https://github.com/soybeanjs/soybean-admin) 156 | - [后台集合](http://vue.easydo.work/) 157 | - [Arco Design Pro](https://github.com/arco-design/arco-design-pro-vue) 158 | - [fantastic-admin](https://fantastic-admin.hurui.me/) 159 | - [AdminLTE](https://github.com/ColorlibHQ/AdminLTE) 160 | - [gin-vue-admin](https://github.com/flipped-aurora/gin-vue-admin) 161 | - [panis-admin](https://github.com/paynezhuang/panis-admin) 162 | 163 | #### 交互参考 164 | 165 | - [mineadmin](https://www.mineadmin.com/) 166 | - [kottster](https://kottster.app/) 167 | - [naive-ui-pro](https://naive-ui-pro.pro-components.cn/home) 168 | - [metis-ui](https://github.com/metisjs/metis-ui) 169 | - [FinCRM](https://github.com/MrXujiang/FinCRM) 170 | - [nuxt-ui-pro/dashboard](https://github.com/nuxt-ui-pro/dashboard) 171 | - [orangeforms](https://www.orangeforms.com/) 172 | - [shop-vite](https://vuejs-core.cn/shop-vite/) 173 | - [semi](https://semi.design/zh-CN/) 174 | - [Cool JS](https://cool-js.com/) 175 | - [Ant Design Pro](https://pro.ant.design/zh-CN/) 176 | - [Ant Design Pro Components](https://procomponents.ant.design/) 177 | - [View UI 专业版](https://pro.iviewui.com/) 178 | - [复盘20+基于React的开源管理后台&插件](https://juejin.cn/post/7304919237983404083) 179 | - [vue-admin-arco](https://github.com/zxwk1998/vue-admin-arco) 180 | -------------------------------------------------------------------------------- /npm/element-plus-admin/components/list/composables/use-list.js: -------------------------------------------------------------------------------- 1 | import { computed, onMounted, reactive, ref } from "vue"; 2 | import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router"; 3 | import { useConsts } from "@/composables/use-consts"; 4 | import { deepCopy } from "jt-helpers"; 5 | import { useHelpers } from "./use-helpers"; 6 | import { ElMessage, ElMessageBox } from "element-plus"; 7 | 8 | export const useList = ({ 9 | onRendered, 10 | routeMode = true, 11 | autoRender = true, 12 | filtersAsKeyValue = false, 13 | pageSize = useConsts().PageSize, 14 | api, 15 | filters = {}, 16 | data = {}, 17 | extraQuery = {}, 18 | mounted = () => {}, 19 | beforeRouteUpdate = () => {}, 20 | } = {}) => { 21 | const route = useRoute(); 22 | const router = useRouter(); 23 | const { formatFilters, encode, decode } = useHelpers(); 24 | const list = reactive({ items: [], total: 0 }); 25 | const loading = ref(false); 26 | const currentPageSize = ref(pageSize); 27 | const currentPage = ref(1); 28 | const cFilters = reactive({ 29 | ref: (el) => (filtersRef = el), 30 | ...filters, 31 | }); 32 | const filtersModel = filters.model; 33 | 34 | let filtersRef = null; 35 | 36 | const state = reactive({ 37 | checkAll: false, 38 | checkedItems: {}, 39 | }); 40 | 41 | const checkedIds = computed(() => 42 | Object.keys(state.checkedItems) 43 | .filter((key) => state.checkedItems[key]) 44 | .map((item) => parseInt(item, 10)) 45 | ); 46 | 47 | const getQuery = (query) => { 48 | const { 49 | currentPageSize = pageSize, 50 | currentPage = 1, 51 | filters = deepCopy(filtersModel), 52 | } = decode(query.$list); 53 | 54 | return { currentPageSize, currentPage, filters }; 55 | }; 56 | 57 | const render = async ({ 58 | currentPageSize = pageSize, 59 | currentPage = 1, 60 | filters = deepCopy(filtersModel), 61 | } = {}) => { 62 | const query = { 63 | offset: (currentPage - 1) * currentPageSize, 64 | limit: currentPageSize, 65 | ...data, 66 | }; 67 | 68 | if (filtersAsKeyValue) { 69 | Object.assign(query, filters); 70 | } else { 71 | query.where = formatFilters(filters); 72 | } 73 | 74 | const { items, total, ...extra } = await api.get({ 75 | query: { 76 | ...query, 77 | ...(typeof extraQuery === "function" ? extraQuery() : extraQuery), 78 | }, 79 | }); 80 | 81 | list.items = items; 82 | list.total = total; 83 | list.extra = extra; 84 | 85 | onRendered && onRendered(); 86 | }; 87 | 88 | const initialize = async ({ filters = deepCopy(filtersModel) }) => { 89 | currentPageSize.value = pageSize; 90 | currentPage.value = 1; 91 | cFilters.model = filters; 92 | await render({ filters }); 93 | }; 94 | 95 | onMounted(async () => { 96 | await mounted(); 97 | 98 | if (!autoRender) return; 99 | 100 | const query = getQuery(route.query); 101 | 102 | currentPageSize.value = query.currentPageSize; 103 | currentPage.value = query.currentPage; 104 | cFilters.model = query.filters; 105 | 106 | await render(query); 107 | }); 108 | 109 | onBeforeRouteUpdate(async (to, from, next) => { 110 | await beforeRouteUpdate(to, from); 111 | 112 | if (!routeMode) return; 113 | 114 | filtersRef && filtersRef.resetFields(); 115 | 116 | const query = getQuery(to.query); 117 | 118 | currentPageSize.value = query.currentPageSize; 119 | currentPage.value = query.currentPage; 120 | cFilters.model = query.filters; 121 | 122 | await render(query); 123 | 124 | next(); 125 | }); 126 | 127 | const reRender = async () => { 128 | await render(getQuery(route.query)); 129 | }; 130 | 131 | const search = async (filters = {}) => { 132 | filtersRef && 133 | filtersRef.validate(async (valid) => { 134 | if (!valid) return; 135 | 136 | loading.value = true; 137 | 138 | if (routeMode) { 139 | await router.replace({ 140 | query: { 141 | ...route.query, 142 | $list: encode({ 143 | currentPageSize: currentPageSize.value, 144 | currentPage: 1, 145 | filters: { ...cFilters.model, ...filters }, 146 | }), 147 | }, 148 | }); 149 | } else { 150 | currentPage.value = 1; 151 | cFilters.model = { ...cFilters.model, ...filters }; 152 | 153 | await render({ 154 | currentPageSize: currentPageSize.value, 155 | currentPage: 1, 156 | filters: cFilters.model, 157 | }); 158 | } 159 | 160 | loading.value = false; 161 | }); 162 | }; 163 | 164 | const del = async ({ id }) => { 165 | await api.delete({ id }); 166 | ElMessage.success("删除成功"); 167 | await reRender(); 168 | }; 169 | 170 | const bulkDel = async () => { 171 | if (!checkedIds.value.length) { 172 | ElMessage.error("未选中数据"); 173 | return; 174 | } 175 | 176 | await ElMessageBox.confirm("确认删除选中数据?", "提示", { 177 | confirmButtonText: "删除", 178 | confirmButtonClass: "el-button--danger", 179 | cancelButtonText: "取消", 180 | type: "warning", 181 | }); 182 | 183 | await api.post({ 184 | action: "bulkDestroy", 185 | body: { ids: checkedIds.value }, 186 | }); 187 | ElMessage.success("删除成功"); 188 | await reRender(); 189 | }; 190 | 191 | const onPageChange = async (current) => { 192 | if (!loading.value) { 193 | if (routeMode) { 194 | const query = getQuery(route.query); 195 | 196 | await router.replace({ 197 | query: { 198 | ...route.query, 199 | $list: encode({ 200 | ...query, 201 | currentPageSize: currentPageSize.value, 202 | currentPage: current, 203 | }), 204 | }, 205 | }); 206 | } else { 207 | loading.value = true; 208 | currentPage.value = current; 209 | await render({ 210 | currentPageSize: currentPageSize.value, 211 | currentPage: current, 212 | filters: cFilters.model, 213 | }); 214 | loading.value = false; 215 | } 216 | } 217 | }; 218 | 219 | const onPageSizeChange = async (pageSize) => { 220 | if (!loading.value) { 221 | if (routeMode) { 222 | currentPageSize.value = pageSize; 223 | currentPage.value = 1; 224 | 225 | const query = getQuery(route.query); 226 | 227 | await router.replace({ 228 | query: { 229 | ...route.query, 230 | $list: encode({ 231 | ...query, 232 | currentPageSize: pageSize, 233 | currentPage: 1, 234 | }), 235 | }, 236 | }); 237 | } else { 238 | loading.value = true; 239 | currentPageSize.value = pageSize; 240 | currentPage.value = 1; 241 | 242 | await render({ 243 | currentPageSize: pageSize, 244 | currentPage: 1, 245 | filters: cFilters.model, 246 | }); 247 | loading.value = false; 248 | } 249 | } 250 | }; 251 | 252 | const onCheckAllChange = () => { 253 | list.items.forEach((item) => { 254 | state.checkedItems[item.id + ""] = state.checkAll; 255 | }); 256 | }; 257 | 258 | const onCheckChange = () => { 259 | let isAllChecked = true; 260 | 261 | list.items.forEach((item) => { 262 | if (!state.checkedItems[item.id + ""]) { 263 | isAllChecked = false; 264 | } 265 | }); 266 | 267 | state.checkAll = isAllChecked; 268 | }; 269 | 270 | return { 271 | list, 272 | currentPageSize, 273 | currentPage, 274 | cFilters, 275 | state, 276 | checkedIds, 277 | render, 278 | initialize, 279 | reRender, 280 | search, 281 | del, 282 | bulkDel, 283 | onPageChange, 284 | onPageSizeChange, 285 | onCheckAllChange, 286 | onCheckChange, 287 | }; 288 | }; 289 | --------------------------------------------------------------------------------