diff --git a/admin/.env.development b/admin/.env.development new file mode 100644 index 00000000..59a598c5 --- /dev/null +++ b/admin/.env.development @@ -0,0 +1,4 @@ +NODE_ENV = 'development' + +# Base API +VITE_APP_BASE_URL='https://likeadmin.yixiangonline.com' \ No newline at end of file diff --git a/admin/.env.production b/admin/.env.production new file mode 100644 index 00000000..1e1ea2b3 --- /dev/null +++ b/admin/.env.production @@ -0,0 +1,3 @@ +NODE_ENV = 'production' +# Base API +VITE_APP_BASE_URL='' \ No newline at end of file diff --git a/admin/.eslintrc.js b/admin/.eslintrc.js new file mode 100644 index 00000000..8fee3b36 --- /dev/null +++ b/admin/.eslintrc.js @@ -0,0 +1,156 @@ +module.exports = { + root: true, + env: { + browser: true, + node: true, + es2021: true + }, + parser: 'vue-eslint-parser', + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + // eslint-config-prettier 的缩写 + 'prettier' + ], + parserOptions: { + ecmaVersion: 12, + parser: '@typescript-eslint/parser', + sourceType: 'module', + ecmaFeatures: { + jsx: true + } + }, + // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写 + plugins: ['vue', '@typescript-eslint', 'prettier'], + rules: { + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-var': 'error', + 'prettier/prettier': 'error', + // 禁止出现console + 'no-console': 'warn', + // 禁用debugger + 'no-debugger': 'warn', + // 禁止出现重复的 case 标签 + 'no-duplicate-case': 'warn', + // 禁止出现空语句块 + 'no-empty': 'warn', + // 禁止不必要的括号 + 'no-extra-parens': 'off', + // 禁止对 function 声明重新赋值 + 'no-func-assign': 'warn', + // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码 + 'no-unreachable': 'warn', + // 强制所有控制语句使用一致的括号风格 + curly: 'warn', + // 要求 switch 语句中有 default 分支 + 'default-case': 'warn', + // 强制尽可能地使用点号 + 'dot-notation': 'warn', + // 要求使用 === 和 !== + eqeqeq: 'warn', + // 禁止 if 语句中 return 语句之后有 else 块 + 'no-else-return': 'warn', + // 禁止出现空函数 + 'no-empty-function': 'warn', + // 禁用不必要的嵌套块 + 'no-lone-blocks': 'warn', + // 禁止使用多个空格 + 'no-multi-spaces': 'warn', + // 禁止多次声明同一变量 + 'no-redeclare': 'warn', + // 禁止在 return 语句中使用赋值语句 + 'no-return-assign': 'warn', + // 禁用不必要的 return await + 'no-return-await': 'warn', + // 禁止自我赋值 + 'no-self-assign': 'warn', + // 禁止自身比较 + 'no-self-compare': 'warn', + // 禁止不必要的 catch 子句 + 'no-useless-catch': 'warn', + // 禁止多余的 return 语句 + 'no-useless-return': 'warn', + // 禁止变量声明与外层作用域的变量同名 + 'no-shadow': 'off', + // 允许delete变量 + 'no-delete-var': 'off', + // 强制数组方括号中使用一致的空格 + 'array-bracket-spacing': 'warn', + // 强制在代码块中使用一致的大括号风格 + 'brace-style': 'warn', + // 强制使用骆驼拼写法命名约定 + camelcase: 'warn', + // 强制使用一致的缩进 + indent: 'off', + // 强制在 JSX 属性中一致地使用双引号或单引号 + // 'jsx-quotes': 'warn', + // 强制可嵌套的块的最大深度4 + 'max-depth': 'warn', + // 强制最大行数 300 + // "max-lines": ["warn", { "max": 1200 }], + // 强制函数最大代码行数 50 + // 'max-lines-per-function': ['warn', { max: 70 }], + // 强制函数块最多允许的的语句数量20 + 'max-statements': ['warn', 100], + // 强制回调函数最大嵌套深度 + 'max-nested-callbacks': ['warn', 3], + // 强制函数定义中最多允许的参数数量 + 'max-params': ['warn', 3], + // 强制每一行中所允许的最大语句数量 + 'max-statements-per-line': ['warn', { max: 1 }], + // 要求方法链中每个调用都有一个换行符 + 'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }], + // 禁止 if 作为唯一的语句出现在 else 语句中 + 'no-lonely-if': 'warn', + // 禁止空格和 tab 的混合缩进 + 'no-mixed-spaces-and-tabs': 'warn', + // 禁止出现多行空行 + 'no-multiple-empty-lines': 'warn', + // 禁止出现; + semi: ['warn', 'never'], + // 强制在块之前使用一致的空格 + 'space-before-blocks': 'warn', + // 强制在 function的左括号之前使用一致的空格 + // 'space-before-function-paren': ['warn', 'never'], + // 强制在圆括号内使用一致的空格 + 'space-in-parens': 'warn', + // 要求操作符周围有空格 + 'space-infix-ops': 'warn', + // 强制在一元操作符前后使用一致的空格 + 'space-unary-ops': 'warn', + // 强制在注释中 // 或 /* 使用一致的空格 + // "spaced-comment": "warn", + // 强制在 switch 的冒号左右有空格 + 'switch-colon-spacing': 'warn', + // 强制箭头函数的箭头前后使用一致的空格 + 'arrow-spacing': 'warn', + 'no-var': 'warn', + 'prefer-const': 'warn', + 'prefer-rest-params': 'warn', + 'no-useless-escape': 'warn', + 'no-irregular-whitespace': 'warn', + 'no-prototype-builtins': 'warn', + 'no-fallthrough': 'warn', + 'no-extra-boolean-cast': 'warn', + 'no-case-declarations': 'warn', + 'no-async-promise-executor': 'warn' + }, + globals: { + defineProps: 'readonly', + defineEmits: 'readonly', + defineExpose: 'readonly', + withDefaults: 'readonly' + } +} diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 00000000..73191537 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,25 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/admin/.prettierrc b/admin/.prettierrc new file mode 100644 index 00000000..59cd6f20 --- /dev/null +++ b/admin/.prettierrc @@ -0,0 +1,9 @@ +{ + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none", + "arrowParens": "avoid", + "bracketSpacing": true +} \ No newline at end of file diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 00000000..f5342b7d --- /dev/null +++ b/admin/README.md @@ -0,0 +1,11 @@ +# Vue 3 + Typescript + Vite + +This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 ` + + diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 00000000..cf878abe --- /dev/null +++ b/admin/package.json @@ -0,0 +1,40 @@ +{ + "name": "admin", + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build && ./release.sh", + "preview": "vite preview", + "lint": "eslint --fix --ext .ts,.tsx,.vue,.js,.jsx src/", + "prettier": "prettier --write src" + }, + "dependencies": { + "@element-plus/icons-vue": "^0.2.4", + "@tinymce/tinymce-vue": "^4.0.5", + "clipboard": "^2.0.8", + "echarts": "^5.2.2", + "element-plus": "^1.2.0-beta.6", + "nprogress": "^0.2.0", + "vue": "^3.2.25", + "vue-echarts": "^6.0.0", + "vue-router": "^4.0.0-0", + "vuedraggable": "^4.1.0", + "vuex": "^4.0.0-0" + }, + "devDependencies": { + "@types/node": "^17.0.15", + "@typescript-eslint/eslint-plugin": "^5.14.0", + "@typescript-eslint/parser": "^5.14.0", + "@vitejs/plugin-vue": "^2.0.0", + "axios": "^0.24.0", + "eslint": "^8.10.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-vue": "^8.5.0", + "prettier": "^2.5.1", + "sass": "^1.49.7", + "typescript": "^4.4.4", + "vite": "^2.7.2", + "vue-tsc": "^0.29.8" + } +} diff --git a/admin/public/favicon.ico b/admin/public/favicon.ico new file mode 100644 index 00000000..df36fcfb Binary files /dev/null and b/admin/public/favicon.ico differ diff --git a/admin/release.sh b/admin/release.sh new file mode 100644 index 00000000..1d238e36 --- /dev/null +++ b/admin/release.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# 文件原路径 +srcPath="./dist/" +# 发布路径文件夹 +releasePath="../server/public/admin" + +#删除发布目录下的mobile文件 +rm -r $releasePath +echo "已删除 ==> $releasePath 下的目录文件" +mkdir $releasePath +echo "已新建 ==> $releasePath 目录" + +# 复制打包目录内的文件到发布目录 +cp -r $srcPath/* $releasePath +echo "已复制 $srcPath/* ==> $releasePath" + +cp $releasePath/../favicon.ico $releasePath \ No newline at end of file diff --git a/admin/src/App.vue b/admin/src/App.vue new file mode 100644 index 00000000..99b6caeb --- /dev/null +++ b/admin/src/App.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/admin/src/api/app.ts b/admin/src/api/app.ts new file mode 100644 index 00000000..bae86493 --- /dev/null +++ b/admin/src/api/app.ts @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +export function apiFileCateAdd(params: any) { + return request.post('/file/addCate', params) +} + +export function apiFileCateEdit(params: { id: number; name: string }) { + return request.post('/file/editCate', params) +} + +// 文件分类删除 +export function apiFileCateDelete(params: { id: number }) { + return request.post('/file/delCate', params) +} + +// 文件分类列表 +export function apiFileCateLists(params: any) { + return request.get('/file/listCate', { params }) +} + +// 文件列表 +export function apiFileList(params: any) { + return request.get('/file/lists', { params }) +} + +// 文件删除 +export function apiFileDelete(params: { ids: any[] }) { + return request.post('/file/delete', params) +} + +// 文件移动 +export function apiFileMove(params: { ids: any[]; cid: number }) { + return request.post('/file/move', params) +} + +// 文件重命名 +export function apiFileRename(params: { id: number; name: string }) { + return request.post('/file/rename', params) +} + +// 配置 +export function apiConfig() { + return request.get('/config/getConfig') +} diff --git a/admin/src/api/application.ts b/admin/src/api/application.ts new file mode 100644 index 00000000..1ad47289 --- /dev/null +++ b/admin/src/api/application.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request' + +// 短信通知列表 +export function apiNoticeLists(params: any) { + return request.get('/notice.notice/settingLists', { params }) +} + +// 短信通知详情 +export function apiNoticeDetail(params: any) { + return request.get('/notice.notice/detail', { params }) +} + +// 设置短信通知 +export function apiNoticeEdit(params: any) { + return request.post('/notice.notice/set', params) +} + +// 短信设置列表 +export function apiSmsLists() { + return request.get('/notice.sms_config/getConfig') +} + +// 短信设置详情 +export function apiSmsDetail(params: any) { + return request.get('/notice.sms_config/detail', { params }) +} + +// 设置短信通知 +export function apiSmsEdit(params: any) { + return request.post('/notice.sms_config/setConfig', params) +} diff --git a/admin/src/api/auth.ts b/admin/src/api/auth.ts new file mode 100644 index 00000000..5852385a --- /dev/null +++ b/admin/src/api/auth.ts @@ -0,0 +1,55 @@ +import request from '@/utils/request' +import { terminal } from '@/config/app' + +export function adminLists(params: any) { + return request.get('/auth.admin/lists', { params }) +} + +// 管理员添加 +export function apiAdminAdd(params: any) { + return request.post('/auth.admin/add', params) +} + +export function apiAdminEdit(params: any) { + return request.post('/auth.admin/edit', params) +} + +// 管理员删除 +export function apiAdminDelete(params: { id: number }) { + return request.post('/auth.admin/delete', params) +} + +// 管理员详情 +export function apiAdminDetail(params: any) { + return request.get('/auth.admin/detail', { params }) +} +// 角色列表 +export function apiRoleLists(params: any) { + return request.get('/auth.role/lists', { params }) +} +// 添加角色 +export function apiRoleAdd(params: any) { + return request.post('/auth.role/add', { ...params }) +} +// 编辑角色 +export function apiRoleEdit(params: any) { + return request.post('/auth.role/edit', { ...params }) +} +// 删除角色 +export function apiRoleDel(params: any) { + return request.post('/auth.role/delete', { ...params }) +} +// 角色详情 +export function apiRoleDetail(params: any) { + return request.get('/auth.role/detail', { params }) +} + +// 角色权限菜单 +export function apiConfigGetMenu() { + return request.get('/config/getMenu') +} + +// 角色权限 +export function apiConfigGetAuth() { + return request.get('/config/getAuth') +} diff --git a/admin/src/api/channel/app_store.d.ts b/admin/src/api/channel/app_store.d.ts new file mode 100644 index 00000000..affb37e2 --- /dev/null +++ b/admin/src/api/channel/app_store.d.ts @@ -0,0 +1,17 @@ +/** S APP设置 **/ +export interface AppSettings_Res { + ios_download_url: string, // 苹果APP下载链接 + android_download_url: string, // 安卓APP下载链接 + download_title: string, // APP下载引导文案 + app_id: string, // 开放平台appid + app_secret: string // 开放平台appSecrets +} + +export interface AppSettings_Req { + ios_download_url: string, // 苹果APP下载链接 + android_download_url: string, // 安卓APP下载链接 + download_title: string, // APP下载引导文案 + app_id: string, // 开放平台appid + app_secret: string // 开放平台appSecrets +} +/** E APP设置 **/ diff --git a/admin/src/api/channel/app_store.ts b/admin/src/api/channel/app_store.ts new file mode 100644 index 00000000..2bc9adff --- /dev/null +++ b/admin/src/api/channel/app_store.ts @@ -0,0 +1,11 @@ +import request from "@/utils/request"; +import * as Interface from './channel/app_store.d.ts' + +/** S APP设置 **/ +// 获取APP设置 +export const apiAppSettings = (): Promise => + request.get('/channel.app_setting/getConfig') +// APP设置 +export const apiAppSettingsSet = (data: Interface.AppSettings_Req): Promise => + request.post('/channel.app_setting/setConfig', data) +/** E APP设置 **/ \ No newline at end of file diff --git a/admin/src/api/channel/h5_store.ts b/admin/src/api/channel/h5_store.ts new file mode 100644 index 00000000..b2878e26 --- /dev/null +++ b/admin/src/api/channel/h5_store.ts @@ -0,0 +1,10 @@ +import request from "@/utils/request"; + +/** S H5设置 **/ +// 获取H5设置 +export const apiH5Settings = (): Promise => + request.get('/channel.h5_setting/getConfig') +// H5设置 +export const apiH5SettingsSet = (data: any): Promise => + request.post('/channel.h5_setting/setConfig', data) +/** E H5设置 **/ \ No newline at end of file diff --git a/admin/src/api/channel/mp_toutiao.ts b/admin/src/api/channel/mp_toutiao.ts new file mode 100644 index 00000000..07a38271 --- /dev/null +++ b/admin/src/api/channel/mp_toutiao.ts @@ -0,0 +1,10 @@ +import request from "@/utils/request"; + +/** S 字节小程序设置 **/ +// 获取字节小程序设置 +export const apiToutiaoSetting = () => + request.get('/toutiao.toutiao_setting/getConfig') +// 字节小程序设置 +export const apiToutiaoSettingSet = (data: any) => + request.post('/toutiao.toutiao_setting/setConfig', data) +/** E 字节小程序设置 **/ \ No newline at end of file diff --git a/admin/src/api/channel/mp_wechat.d.ts b/admin/src/api/channel/mp_wechat.d.ts new file mode 100644 index 00000000..6f2d3e08 --- /dev/null +++ b/admin/src/api/channel/mp_wechat.d.ts @@ -0,0 +1,49 @@ +import * as Common from '../common.d.ts' +import {apiMpWeChatMenuSave} from "@/api/channel/mp_wechat"; + +/** S 渠道信息 **/ +export interface MPWeChatConfigInfo_Res extends Common.Indexes { + name: string, // 公众号名称 + original_id: string, // 原始id + qr_code: string, // 二维码 + app_id: string, // APP ID + app_secret: string, // App Secret + url: string, // URL + token: string, // Token + encoding_aes_key: string, // Encoding AES Key + encryption_type: string, // 消息加密方式: 1-明文模式 2-兼容模式 3-安全模式 + business_domain: string, // 业务域名 + js_secure_domain: string, // JS接口安全域名 + web_auth_domain: string, // 网页授权域名 +} + +export interface MPWeChatConfigEdit_Req { + name?: string, // 公众号名称 + original_id?: string, // 原始id + qr_code?: string, // 二维码 + app_id: string, // APP ID + app_secret: string, // App Secret + token?: string, // Token + encoding_aes_key?: string, // Encoding AES Key + encryption_type: string, // 消息加密方式: 1-明文模式 2-兼容模式 3-安全模式 +} + +/** E 渠道信息 **/ + + +/** S 菜单配置 **/ +export interface MPWeChatMenu { + name: string, // 菜单名称 + type: string, // 菜单类型:click-关键字;view-网页;miniprogram-小程序 + key?: string, // 关键字 + url?: string, // 网页URL + appid?: string, // 小程序AppID + pagepath?: string, // 小程序路径 + sub_button?: Array, // 二级菜单 +} + +export interface MPWeChatMenuSave_Req { + menu: Array +} + +/** E 菜单配置 **/ \ No newline at end of file diff --git a/admin/src/api/channel/mp_wechat.ts b/admin/src/api/channel/mp_wechat.ts new file mode 100644 index 00000000..fb5a8d82 --- /dev/null +++ b/admin/src/api/channel/mp_wechat.ts @@ -0,0 +1,60 @@ +import request from "@/utils/request"; +import * as Interface from './mp_wechat.d.ts' + +/** S 渠道设置 **/ +// 获取渠道信息 +export const apiMPWeChatConfigInfo = (): Promise => + request.get('/channel.official_account_setting/getConfig') + +// 编辑渠道信息 +export const apiMpWeChatConfigEdit = (params: Interface.MPWeChatConfigEdit_Req) => + request.post('/channel.official_account_setting/setConfig', params) + +/** E 渠道设置 **/ + + +/** S 菜单设置 **/ +// 获取菜单详情 +export const apiMpWeChatMenuDetail = (): Promise => + request.get('/channel.official_account_menu/detail') + +// 保存菜单配置 +export const apiMpWeChatMenuSave = (params: any) => + request.post('/channel.official_account_menu/save', params) + +// 发布菜单配置 +export const apiMpWeChatMenuPublish = (params: any) => + request.post('/channel.official_account_menu/saveAndPublish', params) +/** E 菜单设置 **/ + + +/** S 回复管理 **/ +// 新增回复(关注/关词词/默认) +export const apiMpWeChatReplyAdd = (params: any): Promise => + request.post('/channel.official_account_reply/add', params) + +// 编辑回复(关注/关键词/默认) +export const apiMpWeChatReplyEdit = (params: any): Promise => + request.post('/channel.official_account_reply/edit', params) + +// 获取回复详情 +export const apiMpWeChatReplyDetail = (params: any): Promise => + request.get('/channel.official_account_reply/detail', {params}) + +// 删除回复 +export const apiMpWeChatReplyDelete = (params: any): Promise => + request.post('/channel.official_account_reply/delete', params) + +// 更新排序 +export const apiMpWeChatReplySort = (params: any): Promise => + request.post('/channel.official_account_reply/sort', params) + +// 回复列表 +export const apiMpWeChatReplyLists = (params: any): Promise => + request.get('/channel.official_account_reply/lists', {params}) + +// 回复列表 +export const apiMpWeChatReplyStatus = (params: any): Promise => + request.post('/channel.official_account_reply/status', params) +/** E 回复管理 **/ + diff --git a/admin/src/api/channel/wechat_app.d.ts b/admin/src/api/channel/wechat_app.d.ts new file mode 100644 index 00000000..7507c92d --- /dev/null +++ b/admin/src/api/channel/wechat_app.d.ts @@ -0,0 +1,32 @@ +/** S 微信小程序设置 **/ +export interface WechatMiniSetting_Res { + name: string, // 小程序名称 + original_id: string, // 原始id + qr_code: string, // 二维码 + app_id: string, + app_secret: string, + request_domain: string, // request合法域名 + socket_domain: string, // socket合法域名 + upload_file_domain: string, // uploadFile合法域名 + download_file_domain: string, // downloadFile合法域名 + udp_domain: string, // udp合法域名 + business_domain: string, // 业务域名 + url: string, + token: string, + encoding_aes_key: string, + encryption_type: 1 | 2 | 3 , // 消息加密方式 1-明文模式 2-兼容模式 3-安全模式 + data_format: 1 | 2 // 数据格式 1-JSON 2-XML +} + +export interface WechatMiniSetting_Req { + name: string, // 小程序名称 + original_id: string, // 原始id + qr_code: string, // 二维码 + app_id: string, + app_secret: string, + token: string, + encoding_aes_key: string, + encryption_type: 1 | 2 | 3 , // 消息加密方式 1-明文模式 2-兼容模式 3-安全模式 + data_format: 1 | 2 // 数据格式 1-JSON 2-XML +} +/** E 微信小程序设置 **/ diff --git a/admin/src/api/channel/wechat_app.ts b/admin/src/api/channel/wechat_app.ts new file mode 100644 index 00000000..28e7ef9d --- /dev/null +++ b/admin/src/api/channel/wechat_app.ts @@ -0,0 +1,11 @@ +import request from "@/utils/request"; +import * as Interface from './wechat_app.d.ts' + +/** S 微信小程序设置 **/ +// 获取微信小程序设置 +export const apiWechatMiniSetting = (): Promise => + request.get('/channel.mnp_settings/getConfig') +// 微信小程序设置 +export const apiWechatMiniSettingSet = (data: Interface.WechatMiniSetting_Req): Promise => + request.post('/channel.mnp_settings/setConfig', data) +/** E 微信小程序设置 **/ \ No newline at end of file diff --git a/admin/src/api/channel/wechat_platform.ts b/admin/src/api/channel/wechat_platform.ts new file mode 100644 index 00000000..82acfb45 --- /dev/null +++ b/admin/src/api/channel/wechat_platform.ts @@ -0,0 +1,11 @@ +import request from "@/utils/request"; + +/** S 微信公众平台设置 **/ +// 获取pc设置 +export const apiWechatPlatformGet = (): Promise => + request.get('/channel.open_setting/getConfig') + +// pc设置 +export const apiWechatPlatformSet = (data: any): Promise => + request.post('/channel.open_setting/setConfig', data) +/** E pc设置 **/ \ No newline at end of file diff --git a/admin/src/api/decoration.ts b/admin/src/api/decoration.ts new file mode 100644 index 00000000..ca008b33 --- /dev/null +++ b/admin/src/api/decoration.ts @@ -0,0 +1,101 @@ +/* 装修管理 */ +import request from '@/utils/request' + +/* 首页装修 Start */ +// 列表 +export function apiHomeMenuLists() { + return request.get('/decorate.menu/lists') +} + +// 商城页面列表 +export function apiShowPage() { + return request.get('/decorate.menu/shopPage') +} + +// 商品分类一级页面 +export function apiGoodsCategoryPage() { + return request.get('/decorate.menu/goodsCategoryPage') +} + +// 详情 +export function apiHomeMenuDetail(params: any) { + return request.get('/decorate.menu/detail', { params }) +} + +// 添加 +export function apiHomeMenuAdd(params: any) { + return request.post('/decorate.menu/add', params) +} + +// 编辑 +export function apiHomeMenuEdit(params: any) { + return request.post('/decorate.menu/edit', params) +} + +// 状态 +export function apiHomeMenuStatusEdit(params: any) { + return request.post('/decorate.menu/status', params) +} + +// 删除 +export function apiHomeMenuDel(params: any) { + return request.post('/decorate.menu/del', params) +} + +/* 首页装修 End */ + +/* 底部导航 Start */ +// 列表 +export function apiTabBarLists() { + return request.get('/decorate.navigation/lists') +} + +// 详情 +export function apiTabBarDetail(params: any) { + return request.get('/decorate.navigation/detail', { params }) +} + +// 编辑 +export function apiTabBarEdit(params: any) { + return request.post('/decorate.navigation/edit', params) +} + +/* 底部导航 End */ + +/* 广告管理 Start */ +// 列表 +export function apiAdLists(params: any) { + return request.get('/ad.ad/lists', { params }) +} + +// 广告位列表 +export function apiAdPositionLists() { + return request.get('/ad.ad_position/lists') +} + +// 详情 +export function apiAdDetail(params: any) { + return request.get('/ad.ad/detail', { params }) +} + +// 添加 +export function apiAdAdd(params: any) { + return request.post('/ad.ad/add', params) +} + +// 删除 +export function apiAdDel(params: any) { + return request.post('/ad.ad/del', params) +} + +// 状态修改 +export function apiAdEditStatus(params: any) { + return request.post('/ad.ad/status', params) +} + +// 编辑 +export function apiAdEdit(params: any) { + return request.post('/ad.ad/edit', params) +} + +/* 广告管理 End */ diff --git a/admin/src/api/information.ts b/admin/src/api/information.ts new file mode 100644 index 00000000..19506374 --- /dev/null +++ b/admin/src/api/information.ts @@ -0,0 +1,70 @@ +import request from '@/utils/request' + +/** 资讯分类 Start **/ +// 列表 +export function apiArticleCategoryList(params: any) { + return request.get('/article.articleCategory/lists', { params }) +} + +// 添加 +export function apiArticleCategoryAdd(params: any) { + return request.post('/article.articleCategory/add', params) +} + +// 编辑 +export function apiArticleCategoryEdit(params: any) { + return request.post('/article.articleCategory/edit', params) +} + +// 详情 +export function apiArticleCategoryDetail(params: any) { + return request.get('/article.articleCategory/detail', { params }) +} + +// 删除 +export function apiArticleCategoryDelete(params: any) { + return request.post('/article.articleCategory/delete', params) +} + +// 状态 +export function apiArticleCategoryStatus(params: any) { + return request.post('/article.articleCategory/updateStatus', params) +} +/** 资讯分类 End **/ + +/** 资讯列表 Start **/ +// 列表 +export function apiArticleList(params: any) { + return request.get('/article.article/lists', { params }) +} + +// 添加 +export function apiArticleAdd(params: any) { + return request.post('/article.article/add', params) +} + +// 编辑 +export function apiArticleEdit(params: any) { + return request.post('/article.article/edit', params) +} + +// 详情 +export function apiArticleDetail(params: any) { + return request.get('/article.article/detail', { params }) +} + +// 删除 +export function apiArticleDelete(params: any) { + return request.post('/article.article/delete', params) +} + +// 状态 +export function apiArticleStatus(params: any) { + return request.post('/article.article/updateStatus', params) +} + +// 所有资讯分类 +export function apiAllArticleCategory() { + return request.get('/article.articleCategory/selectArticleCategory') +} +/** 资讯列表 End **/ diff --git a/admin/src/api/setting.ts b/admin/src/api/setting.ts new file mode 100644 index 00000000..95624eea --- /dev/null +++ b/admin/src/api/setting.ts @@ -0,0 +1,65 @@ +import request from '@/utils/request' + +// 获取备案信息 +export function apiGetCopyright() { + return request.get('/setting.web.web_setting/getCopyright') +} +// 设置备案信息 +export function apiSetCopyright(params: any) { + return request.post('/setting.web.web_setting/setCopyright', { ...params }) +} + +// 获取网站信息 +export function apiGetWebsite() { + return request.get('/setting.web.web_setting/getWebsite') +} +// 设置网站信息 +export function apiSetWebsite(params: any) { + return request.post('/setting.web.web_setting/setWebsite', { ...params }) +} + +// 获取政策协议 +export function apiGetProtocol() { + return request.get('/setting.web.web_setting/getAgreement') +} +// 设置政策协议 +export function apiSetProtocol(params: any) { + return request.post('/setting.web.web_setting/setAgreement', params) +} + +// 获取系统环境 +export function apiSystemInfo() { + return request.get('/setting.system.system/info') +} + +/** S 在线客服 **/ +// 获取客服设置 +export const apiCustomerServiceGetConfig = (): Promise => + request.get('/setting.customer_service/getConfig') + +// 设置客服设置 +export const apiCustomerServiceSetConfig = (params: any): Promise => + request.post('/setting.customer_service/setConfig', params) +/** E 在线客服 **/ + +/** S 用户设置 **/ +// 获取用户设置 +export function apiUserConfigGet() { + return request.get('/setting.user.user/getConfig') +} + +// 用户设置 +export function apiUserConfigSet(params: any) { + return request.post('/setting.user.user/setConfig', params) +} + +// 获取登录注册设置 +export function apiLoginConfigGet() { + return request.get('/setting.user.user/getRegisterConfig') +} + +// 登录注册设置 +export function apiLoginConfigSet(params: any) { + return request.post('/setting.user.user/setRegisterConfig', params) +} +/** E 用户设置 **/ diff --git a/admin/src/api/user.ts b/admin/src/api/user.ts new file mode 100644 index 00000000..1313a0e0 --- /dev/null +++ b/admin/src/api/user.ts @@ -0,0 +1,17 @@ +import request from '@/utils/request' +import { terminal } from '@/config/app' + +// 登录 +export function apiLogin(params: { account: string; password: string }) { + return request.post('/login/account', { ...params, terminal }) +} + +// 退出登录 +export function apiLogout() { + return request.post('/login/logout') +} + +// 用户信息 +export function apiUserInfo() { + return request.get('/auth.admin/mySelf') +} diff --git a/admin/src/api/workbench.ts b/admin/src/api/workbench.ts new file mode 100644 index 00000000..689582cb --- /dev/null +++ b/admin/src/api/workbench.ts @@ -0,0 +1,6 @@ +import request from '@/utils/request' + +// 工作台主页 +export function apiWorkbench() { + return request.get('/Workbench/index') +} diff --git a/admin/src/assets/font/demo.css b/admin/src/assets/font/demo.css new file mode 100644 index 00000000..aefd9b84 --- /dev/null +++ b/admin/src/assets/font/demo.css @@ -0,0 +1,539 @@ +/* Logo 字体 */ +@font-face { + font-family: 'iconfont logo'; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') + format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') + format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') + format('svg'); +} + +.logo { + font-family: 'iconfont logo'; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown > p, +.markdown > blockquote, +.markdown > .highlight, +.markdown > ol, +.markdown > ul { + width: 80%; +} + +.markdown ul > li { + list-style: circle; +} + +.markdown > ul li, +.markdown blockquote ul > li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown > ul li p, +.markdown > ol li p { + margin: 0.6em 0; +} + +.markdown ol > li { + list-style: decimal; +} + +.markdown > ol li, +.markdown blockquote ol > li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown > table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown > table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown > table th, +.markdown > table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown > table th { + background: #f7f7f7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown > br, +.markdown > p > br { + clear: both; +} + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*='language-'], +pre[class*='language-'] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*='language-']::-moz-selection, +pre[class*='language-'] ::-moz-selection, +code[class*='language-']::-moz-selection, +code[class*='language-'] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*='language-']::selection, +pre[class*='language-'] ::selection, +code[class*='language-']::selection, +code[class*='language-'] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*='language-'], + pre[class*='language-'] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*='language-'] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} + +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*='language-'] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: 0.7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, 0.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #dd4a68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/admin/src/assets/font/demo_index.html b/admin/src/assets/font/demo_index.html new file mode 100644 index 00000000..9fecb732 --- /dev/null +++ b/admin/src/assets/font/demo_index.html @@ -0,0 +1,282 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + + +

+ +
+
+
    +
  • + +
    quanxian
    +
    
    +
  • + +
  • + +
    setting
    +
    
    +
  • + +
  • + +
    home
    +
    
    +
  • +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

+ 注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol + 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。) +

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1640939015921') format('woff2'),
+       url('iconfont.woff?t=1640939015921') format('woff'),
+       url('iconfont.ttf?t=1640939015921') format('truetype');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

+ "iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 + "iconfont"。 +

+
+
+
+
+
    +
  • + +
    quanxian
    +
    .icon-quanxian
    +
  • + +
  • + +
    setting
    +
    .icon-setting
    +
  • + +
  • + +
    home
    +
    .icon-home
    +
  • +
+
+

font-class 引用

+
+ +

+ font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode + 书写不直观,语意不明确的问题。 +

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • + 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon + 是什么。 +
  • +
  • + 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class + 里面的 Unicode 引用。 +
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

+ " iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 + "iconfont"。 +

+
+
+
+
+
    +
  • + +
    quanxian
    +
    #icon-quanxian
    +
  • + +
  • + +
    setting
    +
    #icon-setting
    +
  • + +
  • + +
    home
    +
    #icon-home
    +
  • +
+
+

Symbol 引用

+
+ +

+ 这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点: +

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • + 通过一些技巧,支持像字体那样,通过 font-size, + color 来调整样式。 +
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+
+
+ + + diff --git a/admin/src/assets/font/iconfont.css b/admin/src/assets/font/iconfont.css new file mode 100644 index 00000000..fb13cbf6 --- /dev/null +++ b/admin/src/assets/font/iconfont.css @@ -0,0 +1,26 @@ +@font-face { + font-family: 'iconfont'; /* Project id 3112541 */ + src: url('iconfont.woff2?t=1640939015921') format('woff2'), + url('iconfont.woff?t=1640939015921') format('woff'), + url('iconfont.ttf?t=1640939015921') format('truetype'); +} + +.iconfont { + font-family: 'iconfont' !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-quanxian:before { + content: '\e658'; +} + +.icon-setting:before { + content: '\e659'; +} + +.icon-home:before { + content: '\e65a'; +} diff --git a/admin/src/assets/font/iconfont.json b/admin/src/assets/font/iconfont.json new file mode 100644 index 00000000..a8e85418 --- /dev/null +++ b/admin/src/assets/font/iconfont.json @@ -0,0 +1,30 @@ +{ + "id": "3112541", + "name": "likesadmin", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "26923968", + "name": "quanxian", + "font_class": "quanxian", + "unicode": "e658", + "unicode_decimal": 58968 + }, + { + "icon_id": "26923969", + "name": "setting", + "font_class": "setting", + "unicode": "e659", + "unicode_decimal": 58969 + }, + { + "icon_id": "26923970", + "name": "home", + "font_class": "home", + "unicode": "e65a", + "unicode_decimal": 58970 + } + ] +} diff --git a/admin/src/assets/font/iconfont.ttf b/admin/src/assets/font/iconfont.ttf new file mode 100644 index 00000000..372aed7e Binary files /dev/null and b/admin/src/assets/font/iconfont.ttf differ diff --git a/admin/src/assets/font/iconfont.woff b/admin/src/assets/font/iconfont.woff new file mode 100644 index 00000000..6cf35b91 Binary files /dev/null and b/admin/src/assets/font/iconfont.woff differ diff --git a/admin/src/assets/font/iconfont.woff2 b/admin/src/assets/font/iconfont.woff2 new file mode 100644 index 00000000..7fa398af Binary files /dev/null and b/admin/src/assets/font/iconfont.woff2 differ diff --git a/admin/src/assets/images/icon_folder.png b/admin/src/assets/images/icon_folder.png new file mode 100644 index 00000000..99b800ff Binary files /dev/null and b/admin/src/assets/images/icon_folder.png differ diff --git a/admin/src/assets/images/no_perm.png b/admin/src/assets/images/no_perm.png new file mode 100644 index 00000000..bb696f7f Binary files /dev/null and b/admin/src/assets/images/no_perm.png differ diff --git a/admin/src/components/del-wrap/index.vue b/admin/src/components/del-wrap/index.vue new file mode 100644 index 00000000..7ba087df --- /dev/null +++ b/admin/src/components/del-wrap/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/admin/src/components/editor/index copy.vue b/admin/src/components/editor/index copy.vue new file mode 100644 index 00000000..2c4a9016 --- /dev/null +++ b/admin/src/components/editor/index copy.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/admin/src/components/editor/index.vue b/admin/src/components/editor/index.vue new file mode 100644 index 00000000..d5fa27f7 --- /dev/null +++ b/admin/src/components/editor/index.vue @@ -0,0 +1,124 @@ + + + + + \ No newline at end of file diff --git a/admin/src/components/footer-btns/index.vue b/admin/src/components/footer-btns/index.vue new file mode 100644 index 00000000..f11a08d7 --- /dev/null +++ b/admin/src/components/footer-btns/index.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/admin/src/components/material-select/file-item.vue b/admin/src/components/material-select/file-item.vue new file mode 100644 index 00000000..79e4db8e --- /dev/null +++ b/admin/src/components/material-select/file-item.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/admin/src/components/material-select/hook.ts b/admin/src/components/material-select/hook.ts new file mode 100644 index 00000000..8517462e --- /dev/null +++ b/admin/src/components/material-select/hook.ts @@ -0,0 +1,162 @@ +import { + apiFileCateAdd, + apiFileCateDelete, + apiFileCateEdit, + apiFileCateLists, + apiFileDelete, + apiFileList, + apiFileMove +} from '@/api/app' +import { usePages } from '@/core/hooks/pages' +import { ElMessage } from 'element-plus' +import { computed, inject, reactive, ref, Ref } from 'vue' + +// 左侧分组的钩子函数 +export function useCate(typeValue: Ref) { + // 分组列表 + const cateLists: Ref = ref([]) + // 选中的分组id + const cateId = ref('') + // 添加分组 + const handleAddCate = (val: string) => { + apiFileCateAdd({ + type: typeValue.value, + pid: 0, + name: val + }).then(() => { + getCateLists() + }) + } + // 编辑分组 + const handleEditCate = (val: string, id: number) => { + apiFileCateEdit({ + id, + name: val + }).then(() => { + getCateLists() + }) + } + // 删除分组 + const handleDeleteCate = (id: number) => { + apiFileCateDelete({ + id + }).then(() => { + getCateLists() + }) + } + // 获取分组列表 + const getCateLists = () => { + return new Promise((resolve, reject) => { + apiFileCateLists({ + type: typeValue.value, + page_type: 1 + }).then((res: any) => { + const item: any[] = [ + { + name: '全部', + id: '' + }, + { + name: '未分组', + id: 0 + } + ] + cateLists.value = res?.lists + cateLists.value.unshift(...item) + resolve(cateLists) + }) + }) + } + return { + cateId, + cateLists, + handleAddCate, + handleEditCate, + handleDeleteCate, + getCateLists + } +} + +// 处理文件的钩子函数 +export function useFile(cateId: Ref, type: Ref, limit: Ref) { + const moveId = ref(0) + const select: Ref = ref([]) + const fileParams = reactive({ + name: '', + type: type, + cid: cateId + }) + const { pager, requestApi, resetPage } = usePages({ + callback: apiFileList, + params: fileParams + }) + + const selectStatus = computed( + () => (id: number) => select.value.find((item: any) => item.id == id) + ) + + const getFileList = () => { + requestApi() + } + const refresh = () => { + resetPage() + } + const batchFileDelete = (id?: number[]) => { + const ids = id ? id : select.value.map((item: any) => item.id) + apiFileDelete({ + ids + }).then(res => { + getFileList() + clearSelect() + }) + } + const batchFileMove = () => { + const ids = select.value.map((item: any) => item.id) + apiFileMove({ + ids, + cid: moveId.value + }).then(res => { + moveId.value = 0 + getFileList() + clearSelect() + }) + } + + const selectFile = (item: any) => { + const index = select.value.findIndex((items: any) => items.id == item.id) + if (index != -1) { + select.value.splice(index, 1) + return + } + if (select.value.length == limit.value) { + if (limit.value == 1) { + select.value = [] + select.value.push(item) + return + } + ElMessage.warning('已达到选择上限') + return + } + select.value.push(item) + } + const clearSelect = () => { + select.value = [] + } + const cancelSelete = (id: number) => { + select.value = select.value.filter(item => item.id != id) + } + return { + moveId, + pager, + fileParams, + select, + getFileList, + refresh, + batchFileDelete, + batchFileMove, + selectFile, + selectStatus, + clearSelect, + cancelSelete + } +} diff --git a/admin/src/components/material-select/index.vue b/admin/src/components/material-select/index.vue new file mode 100644 index 00000000..edc80fb9 --- /dev/null +++ b/admin/src/components/material-select/index.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/admin/src/components/material-select/material.vue b/admin/src/components/material-select/material.vue new file mode 100644 index 00000000..96a9ac5a --- /dev/null +++ b/admin/src/components/material-select/material.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/admin/src/components/pagination/index.vue b/admin/src/components/pagination/index.vue new file mode 100644 index 00000000..36436b2b --- /dev/null +++ b/admin/src/components/pagination/index.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/admin/src/components/popover-input/index.vue b/admin/src/components/popover-input/index.vue new file mode 100644 index 00000000..4df0f20b --- /dev/null +++ b/admin/src/components/popover-input/index.vue @@ -0,0 +1,78 @@ + + + diff --git a/admin/src/components/popup/index.vue b/admin/src/components/popup/index.vue new file mode 100644 index 00000000..c1e02f11 --- /dev/null +++ b/admin/src/components/popup/index.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/admin/src/components/upload/index.vue b/admin/src/components/upload/index.vue new file mode 100644 index 00000000..66703633 --- /dev/null +++ b/admin/src/components/upload/index.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/admin/src/config/app.ts b/admin/src/config/app.ts new file mode 100644 index 00000000..ec249b1c --- /dev/null +++ b/admin/src/config/app.ts @@ -0,0 +1,7 @@ +// 标题 +export const title = 'likeadmin' + +// terminal +export const terminal = 1 +// 版本 +export const version = '1.0.0' diff --git a/admin/src/config/cachekey.ts b/admin/src/config/cachekey.ts new file mode 100644 index 00000000..60e81b1f --- /dev/null +++ b/admin/src/config/cachekey.ts @@ -0,0 +1,3 @@ +export const TOKEN = 'token' +// 缓存key前缀 +export const ACCOUNT = 'account' diff --git a/admin/src/core/directives/index.ts b/admin/src/core/directives/index.ts new file mode 100644 index 00000000..6efdb48a --- /dev/null +++ b/admin/src/core/directives/index.ts @@ -0,0 +1,9 @@ +import { App } from 'vue' + +const modules = import.meta.globEager('./modules/*.ts') +export default (app: App) => { + Object.keys(modules).forEach(key => { + const name = key.replace(/^\.\/(.*)\.\w+$/, '$1') + app.directive(name, modules[key].default) + }) +} diff --git a/admin/src/core/directives/modules/copy.ts b/admin/src/core/directives/modules/copy.ts new file mode 100644 index 00000000..077a38cc --- /dev/null +++ b/admin/src/core/directives/modules/copy.ts @@ -0,0 +1,32 @@ +/** + * copy 复制指令(用于复制文本) + * 指令用法: + * 复制 + * copyValue为需要复制的值 + */ + +import { ElMessage } from 'element-plus' +import Clipboard from 'clipboard' +;(function copyboard() { + const clipboard = new Clipboard('.copy-btn') + + clipboard.on('success', e => { + ElMessage.success('复制成功') + e.clearSelection() + }) + + clipboard.on('error', err => { + console.error(err) + ElMessage.success('复制失败') + }) +})() + +export default { + mounted: (el: HTMLElement, binding: any) => { + el.className = el.className + ' copy-btn' + el.setAttribute('data-clipboard-text', binding.value) + }, + updated: (el: HTMLElement, binding: any) => { + el.setAttribute('data-clipboard-text', binding.value) + } +} diff --git a/admin/src/core/hooks/app.ts b/admin/src/core/hooks/app.ts new file mode 100644 index 00000000..9b0e346d --- /dev/null +++ b/admin/src/core/hooks/app.ts @@ -0,0 +1,14 @@ +import { useStore } from '@/store' +import { useRoute, useRouter } from 'vue-router' + +export function useAdmin() { + const store = useStore() + const route = useRoute() + const router = useRouter() + + return { + store, + route, + router + } +} diff --git a/admin/src/core/hooks/pages.ts b/admin/src/core/hooks/pages.ts new file mode 100644 index 00000000..651d1855 --- /dev/null +++ b/admin/src/core/hooks/pages.ts @@ -0,0 +1,68 @@ +import { deepClone } from '@/utils/util' +import { reactive, toRaw } from 'vue' + +// 分页钩子函数 +interface Options { + page?: number + size?: number + callback: (_arg: any) => Promise + params?: Record +} + +let paramsInit: Record = {} + +export function usePages(options: Options) { + const { page = 1, size = 15, callback, params = {} } = options + // 记录分页初始参数 + paramsInit = Object.assign({}, toRaw(params)) + // 分页数据 + const pager = reactive({ + page, + size, + loading: false, + count: 0, + lists: [] as any[] + }) + // 请求分页接口 + const requestApi = () => { + // 禁止并发请求 + if (pager.loading) { + return Promise.reject() + } + pager.loading = true + return callback({ + page_no: pager.page, + page_size: pager.size, + ...params + }) + .then((res: any) => { + pager.count = res?.count + pager.lists = res?.lists + return Promise.resolve(res) + }) + .catch((err: any) => { + return Promise.reject(err) + }) + .finally(() => { + pager.loading = false + }) + } + // 重置为第一页 + const resetPage = () => { + pager.page = 1 + requestApi() + } + // 重置参数 + const resetParams = () => { + Object.keys(paramsInit).forEach(item => { + params[item] = paramsInit[item] + }) + requestApi() + } + return { + pager, + requestApi, + resetParams, + resetPage + } +} diff --git a/admin/src/env.d.ts b/admin/src/env.d.ts new file mode 100644 index 00000000..5611d02d --- /dev/null +++ b/admin/src/env.d.ts @@ -0,0 +1,19 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} + +declare module 'vuedraggable/src/vuedraggable' { + const d: any + export default d +} + +declare module 'nprogress' { + export function configure(options: any): void + export function start(): void + export function done(): void +} diff --git a/admin/src/layout/components/layout-aside/index.vue b/admin/src/layout/components/layout-aside/index.vue new file mode 100644 index 00000000..e67cc9a2 --- /dev/null +++ b/admin/src/layout/components/layout-aside/index.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/admin/src/layout/components/layout-aside/sub-menu.vue b/admin/src/layout/components/layout-aside/sub-menu.vue new file mode 100644 index 00000000..cf9f50ed --- /dev/null +++ b/admin/src/layout/components/layout-aside/sub-menu.vue @@ -0,0 +1,38 @@ + + + diff --git a/admin/src/layout/components/layout-header.vue b/admin/src/layout/components/layout-header.vue new file mode 100644 index 00000000..439f586c --- /dev/null +++ b/admin/src/layout/components/layout-header.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/admin/src/layout/components/layout-main.vue b/admin/src/layout/components/layout-main.vue new file mode 100644 index 00000000..b8a7fe97 --- /dev/null +++ b/admin/src/layout/components/layout-main.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/admin/src/layout/components/perm.vue b/admin/src/layout/components/perm.vue new file mode 100644 index 00000000..00d07756 --- /dev/null +++ b/admin/src/layout/components/perm.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/admin/src/layout/index.vue b/admin/src/layout/index.vue new file mode 100644 index 00000000..35241cb0 --- /dev/null +++ b/admin/src/layout/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/admin/src/main.ts b/admin/src/main.ts new file mode 100644 index 00000000..cd9a032a --- /dev/null +++ b/admin/src/main.ts @@ -0,0 +1,26 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import store, { injectionKey } from './store' +import './permission' +import useElement from './plugins/element' +import useVueEcharts from './plugins/vue-echarts' +import vars, { Variables } from './styles/export.module.scss' +import useDirectives from './core/directives' +const app = createApp(App) +app.config.globalProperties.$variables = vars +// element +useElement(app) +// vue-echarts +useVueEcharts(app) +// 添加自定义指令 +useDirectives(app) + +app.use(router).use(store, injectionKey).mount('#app') + +// 声明vue上的属性 +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + $variables: Variables + } +} diff --git a/admin/src/permission.ts b/admin/src/permission.ts new file mode 100644 index 00000000..8d775886 --- /dev/null +++ b/admin/src/permission.ts @@ -0,0 +1,45 @@ +/** + * 权限控制 + */ + +import NProgress from 'nprogress' +import store from './store' +import router, { asyncRoutes } from './router' +import 'nprogress/nprogress.css' + +// NProgress配置 +NProgress.configure({ showSpinner: false }) + +const loginPath = '/login' +const defaultPath = '/' +// 免登录白名单 +const whiteList = ['/login'] + +router.beforeEach(async (to, from, next) => { + NProgress.start() + // 开始 Progress Bar + to.meta?.title && (document.title = to.meta.title as string) + const token = store.getters.token + if (token) { + // 获取用户信息 + if (store.getters.permission == null) { + store.commit('permission/setSidebar', asyncRoutes[0].children) + await store.dispatch('user/getUser') + await store.dispatch('permission/getPermission') + } + if (to.path === loginPath) { + next({ path: defaultPath }) + } else { + next() + } + } else if (whiteList.includes(to.path as string)) { + // 在免登录白名单,直接进入 + next() + } else { + next({ path: loginPath, query: { redirect: to.fullPath } }) + } +}) + +router.afterEach(async (to, from) => { + NProgress.done() +}) diff --git a/admin/src/plugins/element.ts b/admin/src/plugins/element.ts new file mode 100644 index 00000000..384b0c39 --- /dev/null +++ b/admin/src/plugins/element.ts @@ -0,0 +1,11 @@ +import { App } from '@vue/runtime-core' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import * as ElIcons from '@element-plus/icons-vue' +export default (app: App) => { + app.use(ElementPlus, { zIndex: 3000, locale: zhCn }) + // 统一注册Icon图标 + Object.keys(ElIcons).forEach(item => { + app.component(item, ElIcons[item as keyof typeof ElIcons]) + }) +} diff --git a/admin/src/plugins/vue-echarts.ts b/admin/src/plugins/vue-echarts.ts new file mode 100644 index 00000000..af618d83 --- /dev/null +++ b/admin/src/plugins/vue-echarts.ts @@ -0,0 +1,28 @@ +import ECharts from 'vue-echarts' +import { use } from 'echarts/core' +import { App } from '@vue/runtime-core' + +// 手动引入 ECharts 各模块来减小打包体积 +import { CanvasRenderer } from 'echarts/renderers' +import { BarChart, PieChart, LineChart } from 'echarts/charts' +import { + GridComponent, + TooltipComponent, + TitleComponent, + LegendComponent +} from 'echarts/components' + +use([ + CanvasRenderer, + BarChart, + PieChart, + GridComponent, + TooltipComponent, + TitleComponent, + LegendComponent, + LineChart +]) + +export default (app: App) => { + app.component('VChart', ECharts) +} diff --git a/admin/src/router/index.ts b/admin/src/router/index.ts new file mode 100644 index 00000000..3082574b --- /dev/null +++ b/admin/src/router/index.ts @@ -0,0 +1,55 @@ +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' +import workbench from '@/views/workbench/index.vue' +import Layout from '@/layout/index.vue' +import Error404 from '@/views/error/404.vue' +import Error500 from '@/views/error/500.vue' +// Router modules +import setting from './modules/setting' +import permission from './modules/permission' +import decoration from './modules/decoration' +import content from './modules/content' +import channel from './modules/channel' +import application from './modules/application' +export const asyncRoutes: Array = [ + { + path: '/', + redirect: 'workbench', + component: Layout, + children: [ + { + path: '/workbench', + component: workbench, + meta: { title: '工作台', icon: 'icon-home', permission: ['view'] } + }, + decoration, // 装修管理 + application,// 应用管理 + content, // 内容管理 + channel, // 渠道管理 + permission, + setting + ] + } +] + +export const constRoutes: Array = [ + { + path: '/login', + component: () => import('@/views/account/login.vue') + }, + { + path: '/error/500', + component: Error500 + }, + { path: '/:pathMatch(.*)*', name: '404', component: Error404 } +] + +export const getAsyncRoutes = () => { + return asyncRoutes +} + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [...asyncRoutes, ...constRoutes] +}) + +export default router diff --git a/admin/src/router/modules/application.ts b/admin/src/router/modules/application.ts new file mode 100644 index 00000000..6cd782fd --- /dev/null +++ b/admin/src/router/modules/application.ts @@ -0,0 +1,40 @@ +import { RouteRecordRaw, RouterView } from 'vue-router' + +const routes: RouteRecordRaw = { + path: '/application', + redirect: '/application/notification', + component: RouterView, + meta: { title: '应用管理', icon: 'icon-setting' }, + children: [ + { + path: '/application/notification', + redirect: '/application/notification/index', + component: RouterView, + meta: { title: '消息通知' }, + children: [ + { + path: '/application/notification/index', + component: () => import('@/views/application/notification/index.vue'), + meta: { title: '通知设置', permission: ['view'] }, + }, + { + path: '/application/notification/detail', + component: () => import('@/views/application/notification/detail.vue'), + meta: { hidden: true, title: '通知设置', permission: ['view'] }, + }, + { + path: '/application/sms/index', + component: () => import('@/views/application/sms/index.vue'), + meta: { title: '短信设置', permission: ['view'] }, + }, + { + path: '/application/sms/detail', + component: () => import('@/views/application/sms/detail.vue'), + meta: { hidden: true, title: '短信设置', permission: ['view'] }, + }, + ], + }, + ], +} + +export default routes diff --git a/admin/src/router/modules/channel.ts b/admin/src/router/modules/channel.ts new file mode 100644 index 00000000..94241de8 --- /dev/null +++ b/admin/src/router/modules/channel.ts @@ -0,0 +1,106 @@ +import { RouteRecordRaw, RouterView } from "vue-router" +const routes: RouteRecordRaw = { + path: '/channel', + name: 'channel', + meta: { title: '渠道管理', icon: 'icon-setting' }, + component: RouterView, + children: [{ + path: '/channel/mp_wechat', + name: 'mp_wechat', + meta: { + title: '微信公众号', + parentPath: '/channel', + }, + component: RouterView, + children: [{ + path: '/channel/mp_wechat/index', + name: 'mp_wechat_index', + meta: { + title: '渠道设置', + parentPath: '/channel', + permission: ['view'] + }, + component: () => import('@/views/channel/mp_wechat/index.vue'), + }, { + path: '/channel/mp_wechat/menu', + name: 'mp_wechat_menu', + meta: { + title: '菜单管理', + parentPath: '/channel', + permission: ['view'] + }, + component: () => import('@/views/channel/mp_wechat/menu.vue'), + }, { + path: '/channel/mp_wechat/reply/follow_reply', + name: 'follow_reply', + meta: { + title: '关注回复', + parentPath: '/channel', + permission: ['view'] + }, + component: () => import('@/views/channel/mp_wechat/reply/follow_reply.vue'), + }, { + path: '/channel/mp_wechat/reply/keyword_reply', + name: 'keyword_reply', + meta: { + title: '关键字回复', + parentPath: '/channel', + permission: ['view'] + }, + component: () => import('@/views/channel/mp_wechat/reply/keyword_reply.vue'), + }, { + path: '/channel/mp_wechat/reply/default_reply', + name: 'default_reply', + meta: { + title: '默认回复', + parentPath: '/channel', + permission: ['view'] + }, + component: () => import('@/views/channel/mp_wechat/reply/default_reply.vue'), + }, { + path: '/channel/mp_wechat/reply/reply_edit', + name: 'reply_edit', + meta: { + title: '默认编辑', + parentPath: '/channel', + hidden: true, + permission: ['view'] + }, + component: () => import('@/views/channel/mp_wechat/reply/reply_edit.vue'), + }] + }, { + path: '/channel/wechat_app', + name: 'wechat_app', + meta: { + title: '微信小程序', + parentPath: '/channel' + }, + component: () => import('@/views/channel/wechat_app/index.vue') + }, { + path: '/channel/app_store', + name: 'app_store', + meta: { + title: 'APP', + parentPath: '/channel', + }, + component: () => import('@/views/channel/app_store/index.vue'), + }, { + path: '/channel/h5_store', + name: 'h5_store', + meta: { + title: 'H5', + parentPath: '/channel', + }, + component: () => import('@/views/channel/h5_store/index.vue') + }, { + path: '/wechat/wechat_platform', + name: 'wechat_platform', + meta: { + title: '微信开放平台', + parentPath: '/channel', + }, + component: () => import('@/views/channel/wechat_platform/index.vue') + }] +} + +export default routes \ No newline at end of file diff --git a/admin/src/router/modules/content.ts b/admin/src/router/modules/content.ts new file mode 100644 index 00000000..0f1ed60b --- /dev/null +++ b/admin/src/router/modules/content.ts @@ -0,0 +1,66 @@ +import { RouteRecordRaw, RouterView } from 'vue-router' + +const routes: RouteRecordRaw = { + path: '/content', + redirect: '/content/advertising', + component: RouterView, + meta: { title: '内容管理', icon: 'icon-setting' }, + children: [ + // { + // path: '/content/advertising', + // redirect: '/content/advertising/lists', + // component: RouterView, + // meta: { title: '广告管理' }, + // children: [ + // { + // path: '/content/advertising/lists', + // component: () => import('@/views/content/advertising/advertising.vue'), + // meta: { title: '广告列表' }, + // }, + // { + // path: '/content/advertising/advertising_edit', + // component: () => import('@/views/decoration/advertising_edit.vue'), + // meta: { + // title: '广告列表', + // parent: '/content/advertising/lists', + // hidden: true, + // }, + // }, + // { + // path: '/content/advertising/position', + // component: () => import('@/views/content/advertising/position.vue'), + // meta: { title: '广告位' }, + // }, + // ], + // }, + { + path: '/content/information', + redirect: '/content/information/lists', + component: RouterView, + meta: { title: '资讯管理' }, + children: [ + { + path: '/content/information/lists', + component: () => import('@/views/content/information/lists.vue'), + meta: { title: '资讯列表' }, + }, + { + path: '/content/information/information_edit', + component: () => import('@/views/content/information/information_edit.vue'), + meta: { + title: '资讯列表', + parent: '/content/information/lists', + hidden: true, + }, + }, + { + path: '/content/information/position', + component: () => import('@/views/content/information/category.vue'), + meta: { title: '资讯分类' }, + }, + ], + }, + ], +} + +export default routes diff --git a/admin/src/router/modules/decoration.ts b/admin/src/router/modules/decoration.ts new file mode 100644 index 00000000..692355da --- /dev/null +++ b/admin/src/router/modules/decoration.ts @@ -0,0 +1,38 @@ +import { RouteRecordRaw, RouterView } from 'vue-router' + +const routes: RouteRecordRaw = { + path: '/decoration', + redirect: '/decoration/home', + component: RouterView, + meta: { title: '装修管理', icon: 'icon-setting' }, + children: [ + { + path: '/decoration/home', + component: () => import('@/views/decoration/home.vue'), + meta: { title: '首页装修' }, + }, + { + path: '/decoration/home_edit', + component: () => import('@/views/decoration/home_edit.vue'), + meta: { title: '首页装修', parent: '/decoration/home', hidden: true }, + }, + { + path: '/decoration/tabbar', + component: () => import('@/views/decoration/tabbar.vue'), + meta: { title: '底部标签栏' }, + }, + + { + path: '/decoration/advertising', + component: () => import('@/views/decoration/advertising.vue'), + meta: { title: '广告管理' }, + }, + { + path: '/decoration/advertising_edit', + component: () => import('@/views/decoration/advertising_edit.vue'), + meta: { title: '广告管理', parent: '/decoration/advertising', hidden: true }, + }, + ], +} + +export default routes diff --git a/admin/src/router/modules/permission.ts b/admin/src/router/modules/permission.ts new file mode 100644 index 00000000..c39cfb86 --- /dev/null +++ b/admin/src/router/modules/permission.ts @@ -0,0 +1,40 @@ +import { RouteRecordRaw, RouterView } from 'vue-router' + +const routes: RouteRecordRaw = { + path: '/permission', + redirect: '/permission/admin', + component: RouterView, + meta: { title: '权限管理', icon: 'icon-quanxian' }, + children: [ + { + path: '/permission/admin', + component: () => import('@/views/permission/admin/index.vue'), + meta: { title: '管理员', permission: ['view'] } + }, + { + path: '/permission/admin/edit', + component: () => import('@/views/permission/admin/edit.vue'), + meta: { + title: '管理员', + parent: '/permission/admin', + hidden: true + } + }, + { + path: '/permission/role', + component: () => import('@/views/permission/role/index.vue'), + meta: { title: '角色', permission: ['view'] } + }, + { + path: '/permission/role/edit', + component: () => import('@/views/permission/role/edit.vue'), + meta: { + title: '角色', + parent: '/permission/role', + hidden: true + } + } + ] +} + +export default routes diff --git a/admin/src/router/modules/setting.ts b/admin/src/router/modules/setting.ts new file mode 100644 index 00000000..1786f186 --- /dev/null +++ b/admin/src/router/modules/setting.ts @@ -0,0 +1,99 @@ +import { RouteRecordRaw, RouterView } from 'vue-router' + +const routes: RouteRecordRaw = { + path: '/setting', + redirect: '/setting/service', + component: RouterView, + meta: { title: '系统设置', icon: 'icon-setting' }, + children: [ + { + path: '/setting/service', + redirect: '/setting/service/online_service', + component: RouterView, + meta: { title: '客服设置' }, + children: [ + { + path: '/setting/service/online_service', + component: () => import('@/views/setting/service/online_service.vue'), + meta: { + title: '在线客服', + }, + }, + ], + }, + { + path: '/setting/website', + redirect: '/setting/website/information', + component: RouterView, + meta: { title: '网站设置' }, + children: [ + { + path: '/setting/website/information', + component: () => import('@/views/setting/website/information.vue'), + meta: { + title: '网站信息', + permission: ['view'], + }, + }, + { + path: '/setting/website/filing', + component: () => import('@/views/setting/website/filing.vue'), + meta: { + title: '备案信息', + permission: ['view'], + }, + }, + { + path: '/setting/website/protocol', + component: () => import('@/views/setting/website/protocol.vue'), + meta: { + title: '政策/协议', + permission: ['view'], + }, + }, + ], + }, + { + path: '/setting/user', + redirect: '/setting/user', + component: RouterView, + meta: { title: '用户设置' }, + children: [ + { + path: '/setting/user', + component: () => import('@/views/setting/user/index.vue'), + meta: { + title: '用户设置', + permission: ['view'], + }, + }, + { + path: '/setting/user/login', + component: () => import('@/views/setting/user/login.vue'), + meta: { + title: '登录注册', + permission: ['view'], + }, + }, + ], + }, + { + path: '/setting/system', + redirect: '/setting/system/environment', + component: RouterView, + meta: { title: '系统维护' }, + children: [ + { + path: '/setting/website/environment', + component: () => import('@/views/setting/system/environment.vue'), + meta: { + title: '系统环境', + permission: ['view'], + }, + }, + ], + }, + ], +} + +export default routes diff --git a/admin/src/store/getters.ts b/admin/src/store/getters.ts new file mode 100644 index 00000000..6768e95e --- /dev/null +++ b/admin/src/store/getters.ts @@ -0,0 +1,16 @@ +import { GetterTree } from 'vuex' +import { rootState } from './modules' + +const getters: GetterTree = { + // token + token: state => state.user.token, + // 管理员信息 + userInfo: state => state.user.user, + // 通用配置 + config: state => state.app.config, + // 权限列表 + permission: state => state.permission.permission, + isAdmin: state => state.permission.isAdmin +} + +export default getters diff --git a/admin/src/store/index.ts b/admin/src/store/index.ts new file mode 100644 index 00000000..8499f86a --- /dev/null +++ b/admin/src/store/index.ts @@ -0,0 +1,17 @@ +import { createStore, Store, useStore as baseUseStore } from 'vuex' +import { InjectionKey } from 'vue' +import getters from './getters' +import modules, { rootState } from './modules' + +const store = createStore({ + modules: modules, + getters +}) + +export const injectionKey: InjectionKey> = Symbol('vue-store') + +// 定义自己的 `useStore` 组合式函数 +export function useStore() { + return baseUseStore(injectionKey) +} +export default store diff --git a/admin/src/store/modules/app.ts b/admin/src/store/modules/app.ts new file mode 100644 index 00000000..e8b27d2e --- /dev/null +++ b/admin/src/store/modules/app.ts @@ -0,0 +1,29 @@ +import { apiConfig } from '@/api/app' +import { Module } from 'vuex' +export interface AppModule { + config: any +} + +const app: Module = { + namespaced: true, + state: { + config: {} + }, + mutations: { + setConfig(state, data) { + state.config = data + } + }, + actions: { + getConfig({ commit }) { + return new Promise((resolve, reject) => { + apiConfig().then(data => { + commit('setConfig', data) + resolve(data) + }) + }) + } + } +} + +export default app diff --git a/admin/src/store/modules/index.ts b/admin/src/store/modules/index.ts new file mode 100644 index 00000000..6bee32a6 --- /dev/null +++ b/admin/src/store/modules/index.ts @@ -0,0 +1,14 @@ +import app, { AppModule } from './app' +import permission, { PermissionModule } from './permission' +import user, { UserModule } from './user' +export interface rootState { + app: AppModule + permission: PermissionModule + user: UserModule +} + +export default { + app, + permission, + user +} diff --git a/admin/src/store/modules/permission.ts b/admin/src/store/modules/permission.ts new file mode 100644 index 00000000..a6be6bf0 --- /dev/null +++ b/admin/src/store/modules/permission.ts @@ -0,0 +1,46 @@ +import { Module } from 'vuex' +import { RouteRecordRaw } from 'vue-router' +import { apiConfigGetAuth } from '@/api/auth' +export interface PermissionModule { + sidebar: Array + permission: any[] | null + isAdmin: number +} + +const permission: Module = { + namespaced: true, + state: { + // 左侧菜单 + sidebar: [], + // 权限列表 + permission: null, + // 是否是管理员 + isAdmin: 0 + }, + getters: {}, + mutations: { + setSidebar(state, data) { + state.sidebar = data + }, + setPermission(state, data) { + state.permission = data.auth + state.isAdmin = data.root + } + }, + actions: { + getPermission({ commit }) { + return new Promise((resolve, reject) => { + apiConfigGetAuth() + .then(data => { + commit('setPermission', data) + resolve(data) + }) + .catch(err => { + reject(err) + }) + }) + } + } +} + +export default permission diff --git a/admin/src/store/modules/user.ts b/admin/src/store/modules/user.ts new file mode 100644 index 00000000..3554ad0d --- /dev/null +++ b/admin/src/store/modules/user.ts @@ -0,0 +1,73 @@ +import { Module } from 'vuex' +import cache from '@/utils/cache' +import { TOKEN } from '@/config/cachekey' +import { apiLogin, apiLogout, apiUserInfo } from '@/api/user' +export interface UserModule { + token: string + user: object +} +const user: Module = { + namespaced: true, + state: { + token: cache.get(TOKEN) || '', + user: {} + }, + mutations: { + setToken(state, data) { + state.token = data + cache.set(TOKEN, data) + }, + setUser(state, data) { + state.user = data + } + }, + actions: { + // 登录 + login({ commit }, data) { + const { account, password } = data + return new Promise((resolve, reject) => { + apiLogin({ + account: account.trim(), + password: password + }) + .then((data: any) => { + commit('setToken', data.token) + resolve(data) + }) + .catch(error => { + reject(error) + }) + }) + }, + // 退出登录 + logout({ commit }) { + return new Promise((resolve, reject) => { + apiLogout() + .then(data => { + commit('setToken', '') + commit('setUser', {}) + cache.remove(TOKEN) + resolve(data) + }) + .catch(error => { + reject(error) + }) + }) + }, + // 获取管理员信息 + getUser({ commit }) { + return new Promise((resolve, reject) => { + apiUserInfo() + .then(data => { + commit('setUser', data) + resolve(data) + }) + .catch(error => { + reject(error) + }) + }) + } + } +} + +export default user diff --git a/admin/src/styles/common.scss b/admin/src/styles/common.scss new file mode 100644 index 00000000..3a8af2eb --- /dev/null +++ b/admin/src/styles/common.scss @@ -0,0 +1,298 @@ +/** +公共样式 + */ + +/** S 背景颜色 **/ + +.bg-primary { + background-color: $color-primary; +} + +.bg-success { + background-color: $color-success; +} + +.bg-warning { + background-color: $color-warning; +} + +.bg-danger { + background-color: $color-danger; +} + +.bg-white { + background-color: $color-white; +} + +.bg-info { + background-color: $color-info; +} + +/** E 背景颜色 **/ + +/** E 字体颜色 **/ + +.primary { + color: $color-text-primary; +} + +.white { + color: $color-white; +} + +.black { + color: $color-black; +} + +.normal { + color: $color-text-primary; +} + +.lighter { + color: $color-text-regular; +} + +.muted { + color: $color-text-secondary; +} + +.blue { + color: $color-primary; +} + +.green { + color: $color-success; +} + +/** E 字体颜色 **/ + +/** S Font **/ + +// XL +.xl { + font-size: $font-size-xl; +} + +// LG +.lg { + font-size: $font-size-lg; +} + +// MD +.md { + font-size: $font-size-md; +} + +// NR +.nr { + font-size: $font-size-nr; +} + +// SM +.sm { + font-size: $font-size-sm; +} + +// SM +.xs { + font-size: $font-size-xs; +} + +// 字体大小 Example: f-s-[19-40] +@for $i from 19 through 40 { + .f-s-#{$i} { + font-size: $i + px; + } +} + +// 字体字重 Example: f-w-[100-900] +@for $i from 100 through 900 { + @if $i % 100==0 { + .f-w-#{$i} { + font-weight: $i; + } + } +} + +/** S Font **/ + +// 内、外边距[1-60] +@for $i from 0 through 60 { + // 只要偶数和能被5整除的数 + @if $i % 2==0 or $i % 5==0 { + // 如:m-30 + .m-#{$i} { + margin: $i + px; + } + // 如:p-30 + .p-#{$i} { + padding: $i + px; + } + @each $short, $long in l left, t top, r right, b bottom { + //如: m-l-6 + // 外边距 + .m-#{$short}-#{$i} { + margin-#{$long}: $i + px; + } + //如: p-l-30 + // 内边距 + .p-#{$short}-#{$i} { + padding-#{$long}: $i + px; + } + } + } +} + +/** S 文本行数限制 **/ + +.line-1 { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.line-2 { + -webkit-line-clamp: 2; +} + +.line-3 { + -webkit-line-clamp: 3; +} + +.line-4 { + -webkit-line-clamp: 4; +} + +.line-2, +.line-3, +.line-4 { + overflow: hidden; + word-break: break-all; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; +} + +/** E 文本行数限制 **/ + +/** S 内容排序方式 **/ +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} +/** E 内容排序方式 **/ + +/** S Flex-弹性布局 **/ + +.flex { + display: flex; + flex-direction: row; +} + +.flex-inline { + display: inline-flex; +} + +.flex-col { + flex-direction: column; +} + +.flex-center { + align-items: center; + justify-content: center; +} +.flex-none { + flex: none; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + +.col-baseline { + align-items: baseline; +} + +.col-center { + align-items: center; +} + +.col-top { + align-items: flex-start; +} + +.col-bottom { + align-items: flex-end; +} + +.col-stretch { + align-items: stretch; +} + +.row-center { + justify-content: center; +} + +.row-left { + justify-content: flex-start; +} + +.row-right { + justify-content: flex-end; +} + +.row-between { + justify-content: space-between; +} + +.row-around { + justify-content: space-around; +} + +// Example: flex-[0-24] +@for $i from 0 through 24 { + .flex-#{$i} { + flex: $i; + } +} + +/** E Flex-弹性布局 **/ + +// 行内块元素 +.inline { + display: inline-block; +} + +// 块元素 +.block { + display: block; +} + +// 触手 +.pointer { + cursor: pointer; +} + +// 块卡片 +.ls-card { + border-radius: 8px; + background-color: $color-white; +} + +.clearfix:after { + content: ''; + display: block; + clear: both; + visibility: hidden; +} diff --git a/admin/src/styles/element.scss b/admin/src/styles/element.scss new file mode 100644 index 00000000..92ef86da --- /dev/null +++ b/admin/src/styles/element.scss @@ -0,0 +1,99 @@ +// 引入所有样式 +@use 'element-plus/theme-chalk/src/index.scss'; + +:root { + --el-font-weight-primary: 400; +} + +.el-menu { + border-right: none !important; + + .el-menu--horizontal { + border-bottom: none; + } +} + +.ls-scrollbar.el-scrollbar .el-scrollbar__wrap { + overflow-x: hidden; +} + +.ls-view .el-tabs .el-tabs__item { + height: 60px; + line-height: 60px; +} + +.el-tabs .el-tabs__nav-wrap::after { + height: 1px; + background-color: #e5e5e5; +} + +.el-input-group__prepend { + background-color: #fff; +} + +.el-loading-spinner { + font-size: 26px; +} + +.el-textarea .el-textarea__inner { + resize: none; +} + +.el-image .el-image__error { + font-size: 12px; +} + +// 弹窗居中 +.el-overlay-dialog { + display: flex; + justify-content: center; + align-items: center; + min-width: 1000px; + min-height: 100%; + position: static; + overflow: hidden; + .el-dialog { + --el-dialog-content-font-size: var(--el-font-size-base); + flex: none; + display: flex; + flex-direction: column; + margin: 20px !important; + border-radius: 5px; + + &.body-padding .el-dialog__body { + padding: 0; + } + + .el-dialog__body { + flex: 1; + padding: 15px 20px; + } + } +} + +.el-card { + border: none; +} + +.el-form.ls-form { + .el-form-item__content { + font-size: var(--el-font-size-base); + + & > .el-cascader, + & > .el-select, + & > .el-input { + width: 280px; + } + } +} + +.el-table { + font-size: var(--el-font-size-base); + thead th { + font-weight: 400; + } +} + +.el-icon { + font-size: var(--font-size); +} diff --git a/admin/src/styles/export.module.scss b/admin/src/styles/export.module.scss new file mode 100644 index 00000000..faa2fd20 --- /dev/null +++ b/admin/src/styles/export.module.scss @@ -0,0 +1,13 @@ +:export { + /* 主题颜色 */ + color_primary: $color-primary; + color_success: $color-success; + color_warning: $color-warning; + color_danger: $color-danger; + color_ingo: $color-info; + + /* 字体颜色 */ + font_color_primary: $color-text-primary; + font_color_regular: $color-text-regular; + font_color_secondary: $color-text-secondary; +} diff --git a/admin/src/styles/export.module.scss.d.ts b/admin/src/styles/export.module.scss.d.ts new file mode 100644 index 00000000..280602f4 --- /dev/null +++ b/admin/src/styles/export.module.scss.d.ts @@ -0,0 +1,15 @@ +export interface Variables { + /* 主题颜色 */ + color_primary: string + color_success: string + color_warning: string + color_danger: string + color_ingo: string + + /* 字体颜色 */ + font_color_primary: string + font_color_regular: string + font_color_secondary: string +} +export const variables: Variables +export default variables diff --git a/admin/src/styles/index.scss b/admin/src/styles/index.scss new file mode 100644 index 00000000..beb722b7 --- /dev/null +++ b/admin/src/styles/index.scss @@ -0,0 +1,10 @@ +/** +全局样式 + */ + +// 初始化默认样式 +@import 'reset'; +// 公共样式 +@import 'common'; +// element样式 +@import 'element'; diff --git a/admin/src/styles/reset.scss b/admin/src/styles/reset.scss new file mode 100644 index 00000000..1fee4b7a --- /dev/null +++ b/admin/src/styles/reset.scss @@ -0,0 +1,83 @@ +/* http://meyerweb.com/eric/tools/css/reset/ */ +/* v1.0 | 20080212 */ + +html, +body, +div, +span, +h1, +h2, +h3, +h4, +h5, +h6, +p, +a, +b, +u, +i, +dl, +dt, +dd, +ol, +ul, +li, +input, +form, +label, +table, +tbody, +tfoot, +thead, +tr, +th, +td { + font-family: PingFang SC, Arial, Hiragino Sans GB, Microsoft YaHei, sans-serif; + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; + text-decoration: none; +} + +ol, +ul { + list-style: none; +} + +/* remember to define focus styles! */ +:focus { + outline: 0; +} + +del { + text-decoration: line-through; +} + +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* 初始化页面 */ +body { + font-size: $font-size-base; + background-color: $background-color-base; + color: $color-text-primary; + overflow-y: hidden; + overflow-x: auto; + line-height: 1.3; +} + +input::placeholder { + color: $color-text-placeholder; +} + +/* NProgress */ +#nprogress .bar { + background: $color-primary !important; //自定义颜色 +} diff --git a/admin/src/styles/variables.scss b/admin/src/styles/variables.scss new file mode 100644 index 00000000..0e926fd5 --- /dev/null +++ b/admin/src/styles/variables.scss @@ -0,0 +1,142 @@ +/** +变量 + */ + +/* Color +-------------------------- */ +$color-primary: #4a5dff !default; +$color-success: #67c23a !default; +$color-warning: #fb9400 !default; +$color-danger: #f56c6c !default; +$color-error: #db2828 !default; +$color-info: #909399 !default; +$color-white: #ffffff !default; +$color-black: #333333 !default; + +$color-primary-light-1: mix($color-white, $color-primary, 10%) !default; +$color-primary-light-2: mix($color-white, $color-primary, 20%) !default; +$color-primary-light-3: mix($color-white, $color-primary, 30%) !default; +$color-primary-light-4: mix($color-white, $color-primary, 40%) !default; +$color-primary-light-5: mix($color-white, $color-primary, 50%) !default; +$color-primary-light-6: mix($color-white, $color-primary, 60%) !default; +$color-primary-light-7: mix($color-white, $color-primary, 70%) !default; +$color-primary-light-8: mix($color-white, $color-primary, 80%) !default; +$color-primary-light-9: mix($color-white, $color-primary, 90%) !default; + +/* Font +-------------------------- */ + +$color-text-primary: #333333 !default; +$color-text-regular: #666666 !default; +$color-text-secondary: #999999 !default; +$color-text-placeholder: #999999 !default; +$font-size-xl: 17px !default; +$font-size-lg: 16px !default; +$font-size-md: 15px !default; +$font-size-nr: 14px !default; +$font-size-sm: 13px !default; +$font-size-xs: 12px !default; +$font-size-base: $font-size-sm; + +/* Background +-------------------------- */ + +$background-color-base: #f6f6f6 !default; + +/* Border +-------------------------- */ + +$border-color-base: #e5e5e5 !default; +$border-color-light: #f2f2f2 !default; +$border-width-base: 1px !default; +$border-style-base: solid !default; +$border-color-hover: $color-primary !default; +$border-base: $border-width-base $border-style-base $border-color-base !default; + +/* Layout +-------------------------- */ +$layout-min-width: 1200px !default; +$layout-aside-width: 200px !default; +$layout-header-height: 70px !default; + +$colors: ( + 'primary': ( + 'base': $color-primary + ), + 'success': ( + 'base': $color-success + ), + 'warning': ( + 'base': $color-warning + ), + 'danger': ( + 'base': $color-danger + ), + 'error': ( + 'base': $color-error + ), + 'info': ( + 'base': $color-info + ) +); + +$text-color: ( + 'primary': $color-text-primary, + 'regular': $color-text-regular, + 'secondary': $color-text-secondary, + 'placeholder': $color-text-placeholder +); + +$border-color: ( + 'base': $border-color-base, + 'light': $border-color-light, + 'lighter': $border-color-light, + 'extra-light': $border-color-light +); + +$font-size: ( + 'extra-large': $font-size-xl, + 'large': $font-size-lg, + 'medium': $font-size-md, + 'base': $font-size-base, + 'small': $font-size-sm, + 'extra-small': $font-size-xs +); + +$button-font-size: ( + 'default': $font-size-base, + 'medium': $font-size-base, + 'small': $font-size-base, + 'mini': $font-size-sm +); + +$button-padding-vertical: ( + 'default': 12px, + 'medium': 10px, + 'small': 8px, + 'mini': 6px +); + +$button-padding-horizontal: ( + 'default': 25px, + 'medium': 25px, + 'small': 20px, + 'mini': 15px +); + +$table: ( + 'text-color': $color-text-primary, + 'header-text-color': $color-text-primary, + 'header-bg-color': rgba($color-primary, 0.05) +); + +// 替换elementui的变量 +@forward 'element-plus/theme-chalk/src/common/var.scss' with ( + $colors: $colors, + $text-color: $text-color, + $border-color: $border-color, + $font-size: $font-size, + $button-font-size: $button-font-size, + $button-padding-vertical: $button-padding-vertical, + $table: $table +); diff --git a/admin/src/utils/cache.ts b/admin/src/utils/cache.ts new file mode 100644 index 00000000..d293295e --- /dev/null +++ b/admin/src/utils/cache.ts @@ -0,0 +1,50 @@ +const cache = { + key: 'like_admin_', + //设置缓存(expire为缓存时效) + set(key: string, value: any, expire?: string) { + key = this.getKey(key) + let data: any = { + expire: expire ? this.time() + expire : '', + value + } + + if (typeof data === 'object') { + data = JSON.stringify(data) + } + try { + window.localStorage.setItem(key, data) + } catch (e) { + return false + } + }, + get(key: string) { + key = this.getKey(key) + try { + const data = window.localStorage.getItem(key) + if (!data) { + return false + } + const { value, expire } = JSON.parse(data) + if (expire && expire < this.time()) { + window.localStorage.removeItem(key) + return false + } + return value + } catch (e) { + return false + } + }, + //获取当前时间 + time() { + return Math.round(new Date().getTime() / 1000) + }, + remove(key: string) { + key = this.getKey(key) + window.localStorage.removeItem(key) + }, + getKey(key: string) { + return this.key + key + } +} + +export default cache diff --git a/admin/src/utils/enum.ts b/admin/src/utils/enum.ts new file mode 100644 index 00000000..e69de29b diff --git a/admin/src/utils/request.ts b/admin/src/utils/request.ts new file mode 100644 index 00000000..a89f18df --- /dev/null +++ b/admin/src/utils/request.ts @@ -0,0 +1,86 @@ +'use strict' + +import axios from 'axios' +import { ElMessage } from 'element-plus' +import { version } from '@/config/app' +import store from '@/store' +import { throttle } from './util' +import router from '@/router' +import cache from './cache' +import { TOKEN } from '@/config/cachekey' +// 事件集 +const eventResponse = { + // 成功 + success: ({ show, msg, data }: any): Promise => { + if (show * 1) { + ElMessage({ type: 'success', message: msg }) + } + return data + }, + // 失败 + error: ({ show, msg }: any): Promise => { + if (show * 1) { + ElMessage({ type: 'error', message: msg }) + } + return Promise.reject(msg) + }, + // 重定向 + redirect: throttle(() => { + store.commit('user/setToken', '') + store.commit('user/setUser', {}) + cache.remove(TOKEN) + router.push('/login') + return Promise.reject() + }), + // 打开新的页面 + page: ({ data }: any): Promise => { + window.location.href = data.url + return data + } +} + +const request = axios.create({ + baseURL: `${import.meta.env.VITE_APP_BASE_URL}/adminapi`, + timeout: 60 * 1000, + headers: { + 'Content-Type': 'application/json', + version + } +}) + +request.interceptors.request.use( + config => { + const token = store.getters.token + // header参入Token + if (config.headers) { + config.headers.token = token + } + return config + }, + error => { + return Promise.reject(error) + } +) + +// Add a response interceptor +request.interceptors.response.use( + response => { + switch (response.data.code) { + case 1: + return eventResponse.success(response.data) + case 0: + return eventResponse.error(response.data) + case -1: + return eventResponse.redirect() + case 2: + return eventResponse.page(response.data) + } + }, + error => { + console.log(error) + ElMessage({ type: 'error', message: error }) + return Promise.reject(error) + } +) + +export default request diff --git a/admin/src/utils/type.ts b/admin/src/utils/type.ts new file mode 100644 index 00000000..ee064841 --- /dev/null +++ b/admin/src/utils/type.ts @@ -0,0 +1,5 @@ +// 页面模式 +export enum PageMode { + 'ADD' = 'add', // 添加 + 'EDIT' = 'edit' // 编辑 +} diff --git a/admin/src/utils/util.ts b/admin/src/utils/util.ts new file mode 100644 index 00000000..d40facef --- /dev/null +++ b/admin/src/utils/util.ts @@ -0,0 +1,170 @@ +/** + * 工具方法 + * 请谨慎操作,影响全局 + */ + +/** + * 深拷贝 + * @param {any} target 需要深拷贝的对象 + * @returns {Object} + */ +export function deepClone(target: any) { + if (typeof target !== 'object' || target === null) { + return target + } + + const cloneResult: any = Array.isArray(target) ? [] : {} + + for (const key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + const value = target[key] + + if (typeof value === 'object' && value !== null) { + cloneResult[key] = deepClone(value) + } else { + cloneResult[key] = value + } + } + } + + return cloneResult +} + +/** + * 过滤对象属性 + * @param { Object } target + * @param { Array } filters + * @return { Object } 过滤后的对象 + */ +export function filterObject(target: any, filters: any[]) { + const _target = deepClone(target) + filters.map(key => delete _target[key]) + return _target +} + +/** + * 节流 + * @param { Function } func + * @param { Number } time + * @param context + * @return { Function } + */ +export function throttle(func: () => any, time = 1000, context?: any): any { + let previous = new Date(0).getTime() + return function (...args: []) { + const now = new Date().getTime() + if (now - previous > time) { + previous = now + return func.apply(context, args) + } + } +} + +/** + * Query语法格式化为对象 + * @param { String } str + * @return { Object } + */ +export function queryToObject(str: string) { + const params: any = {} + for (const item of str.split('&')) { + params[item.split('=')[0]] = item.split('=')[1] + } + return params +} + +/** + * 对象格式化为Query语法 + * @param { Object } params + * @return {string} Query语法 + */ +export function objectToQuery(params: any) { + let p = '' + if (typeof params === 'object') { + p = '?' + for (const props in params) { + p += `${props}=${params[props]}&` + } + p = p.slice(0, -1) + } + return p +} + +/** + * @description 获取不重复的id + * @param length { Number } id的长度 + * @return { String } id + */ +export const getNonDuplicateID = (length = 8) => { + let idStr = Date.now().toString(36) + idStr += Math.random().toString(36).substr(3, length) + return idStr +} + +/** + * @description 时间格式化 + * @param dateTime { number } 时间戳 + * @param fmt { string } 时间格式 + * @return { string } + */ +// yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 +export const timeFormat = (dateTime: number, fmt = 'yyyy-mm-dd') => { + // 如果为null,则格式化当前时间 + if (!dateTime) { + dateTime = Number(new Date()) + } + // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式 + if (dateTime.toString().length == 10) { + dateTime *= 1000 + } + const date = new Date(dateTime) + let ret + const opt: any = { + 'y+': date.getFullYear().toString(), // 年 + 'm+': (date.getMonth() + 1).toString(), // 月 + 'd+': date.getDate().toString(), // 日 + 'h+': date.getHours().toString(), // 时 + 'M+': date.getMinutes().toString(), // 分 + 's+': date.getSeconds().toString() // 秒 + } + for (const k in opt) { + ret = new RegExp('(' + k + ')').exec(fmt) + if (ret) { + fmt = fmt.replace( + ret[1], + ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0') + ) + } + } + return fmt +} + +// /** +// * +// * @param {*} tree +// * @param {*} arr +// * @returns +// */ +// export function flatten(tree = [], arr = []) { +// tree.forEach((item) => { +// const { children } = item +// arr.push(item) +// if (children) flatten(children, arr) +// }) +// return arr +// } + +/** + * @description 树状数组扁平化 + * @param { Array } tree 树状结构数组 + * @param { Array } arr 扁平化后的数组 + * @param { String } childrenKey 子节点键名 + * @return { Array } 扁平化后的数组 + */ +export function flatten(tree = [], arr = [], childrenKey = 'children') { + tree.forEach(item => { + const children = item[childrenKey] + children ? flatten(children, arr, childrenKey) : arr.push(item) + }) + return arr +} diff --git a/admin/src/views/account/images/login_bg.png b/admin/src/views/account/images/login_bg.png new file mode 100644 index 00000000..f5867e50 Binary files /dev/null and b/admin/src/views/account/images/login_bg.png differ diff --git a/admin/src/views/account/login.vue b/admin/src/views/account/login.vue new file mode 100644 index 00000000..2eb63235 --- /dev/null +++ b/admin/src/views/account/login.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/admin/src/views/application/notification/detail.vue b/admin/src/views/application/notification/detail.vue new file mode 100644 index 00000000..b000baba --- /dev/null +++ b/admin/src/views/application/notification/detail.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/admin/src/views/application/notification/index.vue b/admin/src/views/application/notification/index.vue new file mode 100644 index 00000000..e8945907 --- /dev/null +++ b/admin/src/views/application/notification/index.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/admin/src/views/application/sms/detail.vue b/admin/src/views/application/sms/detail.vue new file mode 100644 index 00000000..9cebb87d --- /dev/null +++ b/admin/src/views/application/sms/detail.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/admin/src/views/application/sms/index.vue b/admin/src/views/application/sms/index.vue new file mode 100644 index 00000000..1405b691 --- /dev/null +++ b/admin/src/views/application/sms/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/admin/src/views/channel/app_store/index.vue b/admin/src/views/channel/app_store/index.vue new file mode 100644 index 00000000..9f12a09b --- /dev/null +++ b/admin/src/views/channel/app_store/index.vue @@ -0,0 +1,88 @@ + + + + + + diff --git a/admin/src/views/channel/h5_store/index.vue b/admin/src/views/channel/h5_store/index.vue new file mode 100644 index 00000000..ca16df2e --- /dev/null +++ b/admin/src/views/channel/h5_store/index.vue @@ -0,0 +1,73 @@ + + + + + + diff --git a/admin/src/views/channel/index.vue b/admin/src/views/channel/index.vue new file mode 100644 index 00000000..a91d0daf --- /dev/null +++ b/admin/src/views/channel/index.vue @@ -0,0 +1,29 @@ + + + + diff --git a/admin/src/views/channel/mp_wechat/components/menu-form.vue b/admin/src/views/channel/mp_wechat/components/menu-form.vue new file mode 100644 index 00000000..5ef68c34 --- /dev/null +++ b/admin/src/views/channel/mp_wechat/components/menu-form.vue @@ -0,0 +1,126 @@ + + + + + \ No newline at end of file diff --git a/admin/src/views/channel/mp_wechat/index.vue b/admin/src/views/channel/mp_wechat/index.vue new file mode 100644 index 00000000..4248d6dd --- /dev/null +++ b/admin/src/views/channel/mp_wechat/index.vue @@ -0,0 +1,239 @@ + + + + + + \ No newline at end of file diff --git a/admin/src/views/channel/mp_wechat/menu.vue b/admin/src/views/channel/mp_wechat/menu.vue new file mode 100644 index 00000000..13670275 --- /dev/null +++ b/admin/src/views/channel/mp_wechat/menu.vue @@ -0,0 +1,392 @@ + + + + + + \ No newline at end of file diff --git a/admin/src/views/channel/mp_wechat/reply/default_reply.vue b/admin/src/views/channel/mp_wechat/reply/default_reply.vue new file mode 100644 index 00000000..3418134f --- /dev/null +++ b/admin/src/views/channel/mp_wechat/reply/default_reply.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/admin/src/views/channel/mp_wechat/reply/follow_reply.vue b/admin/src/views/channel/mp_wechat/reply/follow_reply.vue new file mode 100644 index 00000000..f06bbb39 --- /dev/null +++ b/admin/src/views/channel/mp_wechat/reply/follow_reply.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/admin/src/views/channel/mp_wechat/reply/keyword_reply.vue b/admin/src/views/channel/mp_wechat/reply/keyword_reply.vue new file mode 100644 index 00000000..53c802b4 --- /dev/null +++ b/admin/src/views/channel/mp_wechat/reply/keyword_reply.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/admin/src/views/channel/mp_wechat/reply/reply_edit.vue b/admin/src/views/channel/mp_wechat/reply/reply_edit.vue new file mode 100644 index 00000000..15721d72 --- /dev/null +++ b/admin/src/views/channel/mp_wechat/reply/reply_edit.vue @@ -0,0 +1,192 @@ + + + + + + diff --git a/admin/src/views/channel/wechat_app/index.vue b/admin/src/views/channel/wechat_app/index.vue new file mode 100644 index 00000000..3bea00d8 --- /dev/null +++ b/admin/src/views/channel/wechat_app/index.vue @@ -0,0 +1,164 @@ + + + + + + diff --git a/admin/src/views/channel/wechat_platform/index.vue b/admin/src/views/channel/wechat_platform/index.vue new file mode 100644 index 00000000..078465ec --- /dev/null +++ b/admin/src/views/channel/wechat_platform/index.vue @@ -0,0 +1,98 @@ + + + + + + diff --git a/admin/src/views/content/advertising/position.vue b/admin/src/views/content/advertising/position.vue new file mode 100644 index 00000000..13d91682 --- /dev/null +++ b/admin/src/views/content/advertising/position.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/admin/src/views/content/information/category.vue b/admin/src/views/content/information/category.vue new file mode 100644 index 00000000..f1749d91 --- /dev/null +++ b/admin/src/views/content/information/category.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/admin/src/views/content/information/components/classify-form.vue b/admin/src/views/content/information/components/classify-form.vue new file mode 100644 index 00000000..b1d43ccf --- /dev/null +++ b/admin/src/views/content/information/components/classify-form.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/admin/src/views/content/information/information_edit.vue b/admin/src/views/content/information/information_edit.vue new file mode 100644 index 00000000..23af1c29 --- /dev/null +++ b/admin/src/views/content/information/information_edit.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/admin/src/views/content/information/lists.vue b/admin/src/views/content/information/lists.vue new file mode 100644 index 00000000..e95fcbd1 --- /dev/null +++ b/admin/src/views/content/information/lists.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/admin/src/views/decoration/advertising.vue b/admin/src/views/decoration/advertising.vue new file mode 100644 index 00000000..65ae3c2c --- /dev/null +++ b/admin/src/views/decoration/advertising.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/admin/src/views/decoration/advertising_edit.vue b/admin/src/views/decoration/advertising_edit.vue new file mode 100644 index 00000000..c210e80c --- /dev/null +++ b/admin/src/views/decoration/advertising_edit.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/admin/src/views/decoration/components/tabbar-edit.vue b/admin/src/views/decoration/components/tabbar-edit.vue new file mode 100644 index 00000000..151c49b0 --- /dev/null +++ b/admin/src/views/decoration/components/tabbar-edit.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/admin/src/views/decoration/home.vue b/admin/src/views/decoration/home.vue new file mode 100644 index 00000000..55890dda --- /dev/null +++ b/admin/src/views/decoration/home.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/admin/src/views/decoration/home_edit.vue b/admin/src/views/decoration/home_edit.vue new file mode 100644 index 00000000..c7a0095c --- /dev/null +++ b/admin/src/views/decoration/home_edit.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/admin/src/views/decoration/tabbar.vue b/admin/src/views/decoration/tabbar.vue new file mode 100644 index 00000000..e28d87aa --- /dev/null +++ b/admin/src/views/decoration/tabbar.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/admin/src/views/error/404.vue b/admin/src/views/error/404.vue new file mode 100644 index 00000000..27ebf845 --- /dev/null +++ b/admin/src/views/error/404.vue @@ -0,0 +1,17 @@ + + + + diff --git a/admin/src/views/error/500.vue b/admin/src/views/error/500.vue new file mode 100644 index 00000000..7d1e7c84 --- /dev/null +++ b/admin/src/views/error/500.vue @@ -0,0 +1,17 @@ + + + + diff --git a/admin/src/views/error/components/error.vue b/admin/src/views/error/components/error.vue new file mode 100644 index 00000000..a5df5fa6 --- /dev/null +++ b/admin/src/views/error/components/error.vue @@ -0,0 +1,60 @@ + + + + diff --git a/admin/src/views/permission/admin/edit.vue b/admin/src/views/permission/admin/edit.vue new file mode 100644 index 00000000..7fd0ac3f --- /dev/null +++ b/admin/src/views/permission/admin/edit.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/admin/src/views/permission/admin/index.vue b/admin/src/views/permission/admin/index.vue new file mode 100644 index 00000000..a5cf2b04 --- /dev/null +++ b/admin/src/views/permission/admin/index.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/admin/src/views/permission/role/edit.vue b/admin/src/views/permission/role/edit.vue new file mode 100644 index 00000000..e4175665 --- /dev/null +++ b/admin/src/views/permission/role/edit.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/admin/src/views/permission/role/index.vue b/admin/src/views/permission/role/index.vue new file mode 100644 index 00000000..a91535e7 --- /dev/null +++ b/admin/src/views/permission/role/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/admin/src/views/setting/service/online_service.vue b/admin/src/views/setting/service/online_service.vue new file mode 100644 index 00000000..4f53262b --- /dev/null +++ b/admin/src/views/setting/service/online_service.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/admin/src/views/setting/system/environment.vue b/admin/src/views/setting/system/environment.vue new file mode 100644 index 00000000..a5a791ca --- /dev/null +++ b/admin/src/views/setting/system/environment.vue @@ -0,0 +1,90 @@ + + + + + + diff --git a/admin/src/views/setting/user/index.vue b/admin/src/views/setting/user/index.vue new file mode 100644 index 00000000..3875eaa4 --- /dev/null +++ b/admin/src/views/setting/user/index.vue @@ -0,0 +1,59 @@ + + + + + + diff --git a/admin/src/views/setting/user/login.vue b/admin/src/views/setting/user/login.vue new file mode 100644 index 00000000..9a428b91 --- /dev/null +++ b/admin/src/views/setting/user/login.vue @@ -0,0 +1,237 @@ + + + + + + diff --git a/admin/src/views/setting/website/filing.vue b/admin/src/views/setting/website/filing.vue new file mode 100644 index 00000000..fd423f15 --- /dev/null +++ b/admin/src/views/setting/website/filing.vue @@ -0,0 +1,138 @@ + + + + + + diff --git a/admin/src/views/setting/website/information.vue b/admin/src/views/setting/website/information.vue new file mode 100644 index 00000000..bf912677 --- /dev/null +++ b/admin/src/views/setting/website/information.vue @@ -0,0 +1,155 @@ + + + + + + diff --git a/admin/src/views/setting/website/protocol.vue b/admin/src/views/setting/website/protocol.vue new file mode 100644 index 00000000..4053062c --- /dev/null +++ b/admin/src/views/setting/website/protocol.vue @@ -0,0 +1,94 @@ + + + diff --git a/admin/src/views/workbench/index.vue b/admin/src/views/workbench/index.vue new file mode 100644 index 00000000..4cfb7905 --- /dev/null +++ b/admin/src/views/workbench/index.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 00000000..3ec85da9 --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "paths": { + "@/*": [ + "./src/*" + ] + }, + "lib": ["esnext", "dom"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/admin/vite.config.ts b/admin/vite.config.ts new file mode 100644 index 00000000..9971d2c4 --- /dev/null +++ b/admin/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + // 引入第三方的配置 + base: '/admin/', + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@use "@/styles/variables.scss" as *;`, + }, + }, + }, + server: { + host: '0.0.0.0' + } +})