├── .editorconfig
├── .env.example
├── .gitignore
├── LICENSE.md
├── README.md
├── babel.config.js
├── certs
└── .gitignore
├── jsconfig.json
├── mocker
├── admin
│ ├── admin-group
│ │ ├── _admin-group-acl.js
│ │ ├── _admin-groups.js
│ │ └── index.js
│ ├── admin
│ │ ├── _admins.js
│ │ └── index.js
│ ├── auth
│ │ ├── _groupAcl.js
│ │ ├── _groupMenus.js
│ │ └── index.js
│ ├── index.js
│ ├── user
│ │ ├── _users.js
│ │ └── index.js
│ └── util.js
└── mobile
│ └── index.js
├── package-lock.json
├── package.json
├── public
├── admin
│ └── index.html
├── common
│ ├── css
│ │ └── spinner.css
│ ├── favicon.ico
│ └── icons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── mstile-144x144.png
│ │ ├── mstile-150x150.png
│ │ └── safari-pinned-tab.svg
├── mobile
│ └── index.html
└── website
│ └── index.html
├── src
├── core
│ ├── assets
│ │ ├── images
│ │ │ └── avatar.png
│ │ └── styles
│ │ │ ├── base.less
│ │ │ └── mixin.less
│ ├── components
│ │ ├── Avatar
│ │ │ └── index.vue
│ │ ├── BlankLayout.vue
│ │ ├── Icon
│ │ │ └── index.vue
│ │ ├── Placeholder
│ │ │ ├── PlaceholderForm.vue
│ │ │ ├── PlaceholderTextBlock.vue
│ │ │ ├── PlaceholderTextRow.vue
│ │ │ └── animation.less
│ │ ├── PrefectScrollbar
│ │ │ └── index.vue
│ │ └── Tinymce
│ │ │ └── index.vue
│ ├── directives
│ │ ├── SameWidth.js
│ │ └── TransformDom.js
│ ├── plugins
│ │ └── HttpClient.js
│ └── utils
│ │ ├── flatry.js
│ │ ├── request.js
│ │ ├── upload.js
│ │ └── util.js
└── modules
│ ├── admin
│ ├── App.vue
│ ├── assets
│ │ ├── images
│ │ │ ├── banner-white.png
│ │ │ ├── banner.png
│ │ │ ├── login-bg.jpg
│ │ │ └── logo.png
│ │ └── styles
│ │ │ ├── default-layout.less
│ │ │ └── style.less
│ ├── components
│ │ ├── AdminLayout.vue
│ │ ├── Breadcrumb.vue
│ │ ├── DistrictCascader.vue
│ │ ├── ExportButton.vue
│ │ ├── HeaderNav.vue
│ │ ├── HeaderTab.vue
│ │ ├── ImageCropper.vue
│ │ ├── LoginForm.vue
│ │ ├── LoginModal.vue
│ │ ├── Pagination.vue
│ │ ├── RemoteSelect.vue
│ │ ├── SearchForm.vue
│ │ ├── SiderMenu.vue
│ │ ├── SiderMenuIcon.vue
│ │ ├── SiderMenuItem.vue
│ │ ├── SiderMenuSub.vue
│ │ └── upload
│ │ │ ├── AvatarUpload.vue
│ │ │ ├── FileUpload.vue
│ │ │ ├── ImageUpload.vue
│ │ │ └── _Upload.vue
│ ├── main.js
│ ├── mixins
│ │ ├── SearchFormFieldsMixin.js
│ │ └── UnprocessableEntityHttpErrorMixin.js
│ ├── plugins
│ │ ├── DeleteDialog.js
│ │ └── PermissionCheck.js
│ ├── registerServiceWorker.js
│ ├── router
│ │ ├── admin
│ │ │ ├── admin-log.js
│ │ │ ├── admin.js
│ │ │ └── group.js
│ │ ├── cms
│ │ │ ├── article.js
│ │ │ └── category.js
│ │ ├── district
│ │ │ └── district.js
│ │ ├── index.js
│ │ ├── shop
│ │ │ ├── brand.js
│ │ │ ├── category.js
│ │ │ └── product.js
│ │ └── user
│ │ │ ├── address.js
│ │ │ └── user.js
│ ├── services
│ │ ├── AdminGroupService.js
│ │ ├── AdminService.js
│ │ ├── AuthService.js
│ │ ├── CmsArticleService.js
│ │ ├── CmsCategoryService.js
│ │ ├── DistrictService.js
│ │ ├── MiscService.js
│ │ ├── OpenService.js
│ │ ├── ShopBrandService.js
│ │ ├── ShopCategoryService.js
│ │ ├── ShopProductService.js
│ │ ├── UserAddressService.js
│ │ └── UserService.js
│ ├── store
│ │ ├── app.js
│ │ ├── auth
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── tabs
│ │ │ └── index.js
│ └── views
│ │ ├── admin-group
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ └── _EditForm.vue
│ │ ├── admin-log
│ │ └── Index.vue
│ │ ├── admin
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ └── _EditForm.vue
│ │ ├── cms-article
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ └── _EditForm.vue
│ │ ├── cms-category
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ ├── Layout.vue
│ │ └── _EditForm.vue
│ │ ├── district
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ ├── Layout.vue
│ │ └── _EditForm.vue
│ │ ├── shop-brand
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ └── _EditForm.vue
│ │ ├── shop-category
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ ├── Layout.vue
│ │ └── _EditForm.vue
│ │ ├── shop-product
│ │ └── Index.vue
│ │ ├── site
│ │ ├── Index.vue
│ │ ├── Login.vue
│ │ ├── NotFound.vue
│ │ └── Profile.vue
│ │ ├── user-address
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ └── _EditForm.vue
│ │ └── user
│ │ ├── Create.vue
│ │ ├── Edit.vue
│ │ ├── Index.vue
│ │ └── _EditForm.vue
│ ├── mobile
│ ├── App.vue
│ ├── assets
│ │ └── images
│ │ │ └── logo.png
│ ├── components
│ │ └── DefaultLayout.vue
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── index.js
│ └── views
│ │ └── site
│ │ ├── Index.vue
│ │ └── NotFound.vue
│ └── website
│ ├── App.vue
│ ├── components
│ └── DefaultLayout.vue
│ ├── entry.client.js
│ ├── entry.server.js
│ ├── main.js
│ ├── router
│ └── index.js
│ └── views
│ └── site
│ └── Index.vue
├── vue.config.js
├── vue.entry.admin.js
├── vue.entry.mobile.js
├── vue.entry.website.client.js
├── vue.entry.website.js
├── vue.entry.website.server.js
└── webstorm.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{js, jsx, ts, tsx, vue, json, scss, css, less}]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | VUE_APP_ADMIN_API_HOST = 'http://localhost:8080/admin/api'
2 | VUE_APP_RESTFUL_API_HOST = 'http://localhost:8081/restful/api'
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
23 | .env.production
24 | .env.development
25 | webpack.entry.*
26 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Huijie Wei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | VUE + ELEMENT.UI 演示项目
2 |
3 | 项目目前正在开发,变动可能会很大,暂时只供研究学习参考
4 |
5 | ### 特点
6 |
7 | 1. 基于 VUE CLI
8 |
9 | 2. 自带 Mock 服务器
10 |
11 | 3. 使用 .env 配置环境变量
12 |
13 | 4. 使用不同配置文件来分割不同项目入口
14 |
15 | ### 在线演示
16 | https://agile.huijiewei.com/admin
17 | 账号:13098761234
18 | 密码:123456
19 |
20 | ### 教程
21 |
22 | #### 安装
23 |
24 | ```bash
25 | npm install
26 | ```
27 |
28 | #### 配置
29 |
30 | ##### 修改 Hosts
31 | ```text
32 | 127.0.0.1 www.agile.test
33 | ```
34 |
35 | #### vue.config.js 配置说明
36 | 可以根据 package.json 文件 scripts 内命令行 VUE_CLI_SERVICE_CONFIG_PATH 参数使用不同项目入口配置文件
37 |
38 |
39 | #### 运行本地开发
40 | 复制 .env.example 为 .env.development
41 |
42 | ```bash
43 | npm run serve:admin
44 | npm run serve:mobile
45 | ```
46 |
47 | #### 构建生产版本
48 |
49 | ```bash
50 | npm run build:admin
51 | npm run build:mobile
52 | ```
53 |
54 | ### 关键目录说明
55 | ```
56 | ├── dist // 生成的生产环境文件
57 | ├── mocker // Mock 服务器和 Mock 数据
58 | ├── src // 源代码目录
59 | │ ├── core // 公用核心模块
60 | │ └── modules // 模块目录
61 | │ └── admin // 管理后台
62 | │ └── mobile // 移动端
63 | ```
64 |
65 | ### 其他说明
66 |
67 | 默认 Mock 服务后台管理员:
68 |
69 | 用户:13012345679
70 | 密码:123456
71 |
72 | ### 后端配合
73 | Agile Spring Boot
74 |
75 | https://github.com/huijiewei/agile-boot
76 |
77 | ### 说明文件版本更新
78 |
79 | Version: 2020-10-31 19:30
80 |
81 | ### Element-UI 的一个小修正
82 | 打开 ./node_modules/element-ui/lib/cascader-panel.js 文件 1053 行
83 | 把 `if(value)` 改为 `if (value !== undefined && value !== null) {`
84 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | }
4 |
--------------------------------------------------------------------------------
/certs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./src/**/*"],
3 | "compilerOptions": {
4 | "baseUrl": "./src",
5 | "paths": {
6 | "@core/*": ["core/*"],
7 | "@admin/*": ["modules/admin/*"],
8 | "@mobile/*": ["modules/mobile/*"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mocker/admin/admin-group/_admin-group-acl.js:
--------------------------------------------------------------------------------
1 | const AdminGroupAcl = [
2 | {
3 | name: '管理首页',
4 | children: [
5 | { name: '测试短信发送', actionId: 'site/sms-send' },
6 | { name: '更新系统缓存', actionId: 'site/clean-cache' },
7 | ],
8 | },
9 | {
10 | name: '会员管理',
11 | children: [
12 | { name: '会员列表', actionId: 'user/index' },
13 | { name: '会员导出', actionId: 'user/export' },
14 | { name: '会员新建', actionId: 'user/create' },
15 | { name: '会员编辑', actionId: 'user/edit' },
16 | { name: '会员删除', actionId: 'user/delete' },
17 | { name: '会员导入', actionId: 'user/import' },
18 | ],
19 | },
20 | {
21 | name: '商品管理',
22 | children: [
23 | {
24 | children: [
25 | { name: '商品列表', actionId: 'good/index' },
26 | { name: '商品导入', actionId: 'good/export' },
27 | { name: '新建商品', actionId: 'good/new' },
28 | { name: '编辑商品', actionId: 'good/edit' },
29 | { name: '删除商品', actionId: 'good/delete' },
30 | ],
31 | },
32 | {
33 | children: [
34 | { name: '商品分类', actionId: 'shop-category/index' },
35 | { name: '商品分类新建', actionId: 'shop-category/create' },
36 | { name: '商品分类编辑', actionId: 'shop-category/edit' },
37 | { name: '商品分类删除', actionId: 'shop-category/delete' },
38 | ],
39 | },
40 | { name: '商品属性', actionId: 'shop/property' },
41 | { name: '商品特点', actionId: 'shop/feature' },
42 | { name: '商品品牌', actionId: 'shop/brand' },
43 | ],
44 | },
45 | {
46 | name: '系统管理',
47 | children: [
48 | {
49 | children: [
50 | { name: '管理员列表', actionId: 'admin/index' },
51 | { name: '管理员新建', actionId: 'admin/create' },
52 | { name: '管理员编辑', actionId: 'admin/edit' },
53 | { name: '管理员删除', actionId: 'admin/delete' },
54 | ],
55 | },
56 | {
57 | children: [
58 | { name: '管理组列表', actionId: 'admin-group/index' },
59 | { name: '管理组新建', actionId: 'admin-group/create' },
60 | { name: '管理组编辑', actionId: 'admin-group/edit' },
61 | { name: '管理组删除', actionId: 'admin-group/delete' },
62 | ],
63 | },
64 | ],
65 | },
66 | ]
67 |
68 | module.exports = AdminGroupAcl
69 |
--------------------------------------------------------------------------------
/mocker/admin/admin-group/_admin-groups.js:
--------------------------------------------------------------------------------
1 | const adminGroups = [
2 | { id: 101, name: '开发组' },
3 | { id: 102, name: '演示组' },
4 | { id: 106, name: '测试组' },
5 | { id: 107, name: '产品组' },
6 | { id: 108, name: '市场组' },
7 | { id: 109, name: '运营组' },
8 | ]
9 |
10 | module.exports = adminGroups
11 |
--------------------------------------------------------------------------------
/mocker/admin/admin-group/index.js:
--------------------------------------------------------------------------------
1 | const { authenticationCheck, notFoundCheck } = require('../util')
2 | const adminGroups = require('./_admin-groups')
3 | const adminGroupAcl = require('./_admin-group-acl')
4 |
5 | exports.adminGroupList = (req, res) => {
6 | const success = {
7 | items: adminGroups,
8 | }
9 |
10 | return authenticationCheck(req, res, () => {
11 | return res.json(success)
12 | })
13 | }
14 |
15 | exports.adminGroupItem = (req, res) => {
16 | const { id } = req.params
17 |
18 | return authenticationCheck(req, res, () => {
19 | const adminGroup = adminGroups.find((item) => {
20 | return item.id === Number(id)
21 | })
22 |
23 | return notFoundCheck(adminGroup, '管理组不存在', res, () => {
24 | const result = JSON.parse(JSON.stringify(adminGroup))
25 |
26 | result.acl = [
27 | 'site/sms-send',
28 | 'site/clean-cache',
29 | 'user/index',
30 | 'user/create',
31 | 'user/import',
32 | 'good/index',
33 | 'good/new',
34 | 'good/edit',
35 | 'good/delete',
36 | 'admin/index',
37 | 'admin/create',
38 | 'admin/edit',
39 | 'admin/delete',
40 | 'admin-group/index',
41 | 'admin-group/create',
42 | 'admin-group/edit',
43 | 'admin-group/delete',
44 | 'user/export',
45 | 'user/edit',
46 | 'user/delete',
47 | 'shop/property',
48 | 'shop/feature',
49 | 'shop/brand',
50 | 'shop-category/index',
51 | 'shop-category/create',
52 | 'shop-category/edit',
53 | 'shop-category/delete',
54 | ]
55 |
56 | return res.json(result)
57 | })
58 | })
59 | }
60 |
61 | exports.adminGroupCreate = (req, res) => {
62 | return authenticationCheck(req, res, () => {
63 | const { name } = req.body
64 |
65 | const errors = []
66 |
67 | const adminGroup = adminGroups.find((item) => {
68 | return item.name === name
69 | })
70 |
71 | if (adminGroup) {
72 | errors.push({ field: 'phone', message: '管理组 ' + name + ' 已存在' })
73 | }
74 |
75 | if (errors.length) {
76 | return res.status(422).json(errors)
77 | }
78 |
79 | return res.json({ message: '新建管理组成功', adminId: 1022 })
80 | })
81 | }
82 |
83 | exports.adminGroupEdit = (req, res) => {
84 | return authenticationCheck(req, res, () => {
85 | return res.json({ message: '编辑管理组成功' })
86 | })
87 | }
88 |
89 | exports.adminGroupDelete = (req, res) => {
90 | return authenticationCheck(req, res, () => {
91 | return res.json({ message: '删除管理组成功' })
92 | })
93 | }
94 |
95 | exports.adminGroupAcl = (req, res) => {
96 | return authenticationCheck(req, res, () => {
97 | return res.json(adminGroupAcl)
98 | })
99 | }
100 |
101 | exports.adminGroupMap = (req, res) => {
102 | return authenticationCheck(req, res, () => {
103 | const map = {}
104 | adminGroups.forEach((adminGroup) => {
105 | map[adminGroup.id] = adminGroup.name
106 | })
107 | return res.json(map)
108 | })
109 | }
110 |
--------------------------------------------------------------------------------
/mocker/admin/admin/_admins.js:
--------------------------------------------------------------------------------
1 | const admins = [
2 | {
3 | id: 1021,
4 | phone: '13012345678',
5 | email: '',
6 | name: '开发账号',
7 | avatar:
8 | 'https://yuncars-other.oss-cn-shanghai.aliyuncs.com//boilerplate/201903/v4md41vswd_7477uudbmg.jpg?x-oss-process=style/avatar',
9 | createdAt: '2018-07-24 11:48:31',
10 | adminGroup: { id: 101, name: '开发组' },
11 | },
12 | {
13 | id: 1022,
14 | phone: '13098761234',
15 | email: '',
16 | name: '演示账号',
17 | avatar:
18 | 'https://yuncars-other.oss-cn-shanghai.aliyuncs.com//boilerplate/201807/150zg7q4u7c.jpg@!avatar',
19 | createdAt: '2018-07-25 10:41:57',
20 | adminGroup: { id: 102, name: '演示组' },
21 | },
22 | {
23 | id: 1023,
24 | phone: '13512345678',
25 | email: '',
26 | name: '测试账号',
27 | avatar:
28 | 'https://yuncars-other.oss-cn-shanghai.aliyuncs.com//boilerplate/201808/blnp6u1xtmq.jpg@!avatar',
29 | createdAt: '2018-08-01 09:09:41',
30 | adminGroup: { id: 102, name: '演示组' },
31 | },
32 | ]
33 |
34 | module.exports = admins
35 |
--------------------------------------------------------------------------------
/mocker/admin/admin/index.js:
--------------------------------------------------------------------------------
1 | const { authenticationCheck, notFoundCheck } = require('../util')
2 | const admins = require('./_admins')
3 | const adminGroups = require('../admin-group/_admin-groups')
4 |
5 | exports.adminList = (req, res) => {
6 | const success = {
7 | items: admins,
8 | }
9 |
10 | return authenticationCheck(req, res, () => {
11 | return res.json(success)
12 | })
13 | }
14 |
15 | exports.adminItem = (req, res) => {
16 | const { id } = req.params
17 |
18 | return authenticationCheck(req, res, () => {
19 | const admin = admins.find((item) => {
20 | return item.id === Number(id)
21 | })
22 |
23 | return notFoundCheck(admin, '管理员不存在', res, () => {
24 | const result = JSON.parse(JSON.stringify(admin))
25 | delete result.group
26 | return res.json(result)
27 | })
28 | })
29 | }
30 |
31 | exports.adminCreate = (req, res) => {
32 | return authenticationCheck(req, res, () => {
33 | const { phone, groupId, password } = req.body
34 |
35 | const errors = []
36 |
37 | const admin = admins.find((item) => {
38 | return item.phone === phone
39 | })
40 |
41 | if (admin) {
42 | errors.push({
43 | field: 'phone',
44 | message: '手机号码 ' + phone + ' 已被占用',
45 | })
46 | }
47 |
48 | if (password.length < 6) {
49 | errors.push({ field: 'password', message: '密码不能少于 6 位' })
50 | }
51 |
52 | const group = adminGroups.find((item) => {
53 | return item.id === Number(groupId)
54 | })
55 |
56 | if (!group) {
57 | errors.push({ field: 'groupId', message: '选择了不存在的用户组' })
58 | }
59 |
60 | if (errors.length) {
61 | return res.status(422).json(errors)
62 | }
63 |
64 | return res.json({ message: '新建管理员成功', adminId: 1022 })
65 | })
66 | }
67 |
68 | exports.adminEdit = (req, res) => {
69 | return authenticationCheck(req, res, () => {
70 | return res.json({ message: '编辑管理员成功' })
71 | })
72 | }
73 |
74 | exports.adminDelete = (req, res) => {
75 | return authenticationCheck(req, res, () => {
76 | return res.json({ message: '删除管理员成功' })
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/mocker/admin/auth/_groupAcl.js:
--------------------------------------------------------------------------------
1 | const groupAcl = [
2 | 'site/sms-send',
3 | 'site/clean-cache',
4 | 'user/index',
5 | 'user/create',
6 | 'user/import',
7 | 'good/index',
8 | 'good/new',
9 | 'good/edit',
10 | 'good/delete',
11 | 'admin/index',
12 | 'admin/create',
13 | 'admin/edit',
14 | 'admin/delete',
15 | 'admin-group/index',
16 | 'admin-group/edit',
17 | 'user/export',
18 | 'user/edit',
19 | 'user/delete',
20 | 'user/delete',
21 | 'shop/property',
22 | 'shop/feature',
23 | 'shop/brand',
24 | 'shop-category/index',
25 | 'shop-category/create',
26 | 'shop-category/edit',
27 | 'shop-category/delete',
28 | 'shop-category/index',
29 | 'shop-category/create',
30 | 'shop-category/edit',
31 | 'shop-category/delete',
32 | 'shop/property',
33 | 'shop/feature',
34 | 'shop/brand',
35 | ]
36 |
37 | module.exports = groupAcl
38 |
--------------------------------------------------------------------------------
/mocker/admin/auth/_groupMenus.js:
--------------------------------------------------------------------------------
1 | const groupMenus = [
2 | {
3 | label: '首页',
4 | icon: 'home',
5 | url: 'site/index',
6 | public: true,
7 | },
8 | {
9 | label: '会员管理',
10 | icon: 'user',
11 | children: [
12 | {
13 | label: '会员列表',
14 | url: 'user/index',
15 | },
16 | {
17 | label: '新建会员',
18 | children: [
19 | {
20 | label: '新建会员',
21 | url: 'user/create',
22 | },
23 | {
24 | label: '批量导入',
25 | url: 'user/import',
26 | },
27 | ],
28 | },
29 | ],
30 | },
31 | {
32 | label: '商品管理',
33 | icon: 'gift',
34 | children: [
35 | {
36 | label: '新建商品',
37 | url: 'good/new',
38 | },
39 | {
40 | label: '商品列表',
41 | url: 'good/index',
42 | },
43 | {
44 | label: '商品分类',
45 | url: 'shop-category/index',
46 | },
47 | {
48 | label: '商品属性',
49 | url: 'shop/property',
50 | },
51 | {
52 | label: '商品特点',
53 | url: 'shop/feature',
54 | },
55 | {
56 | label: '商品品牌',
57 | url: 'shop/brand',
58 | },
59 | ],
60 | },
61 | {
62 | label: '系统管理',
63 | icon: 'setting',
64 | children: [
65 | {
66 | label: '管理员',
67 | url: 'admin/index',
68 | },
69 | {
70 | label: '管理组',
71 | url: 'admin-group/index',
72 | },
73 | ],
74 | },
75 | ]
76 |
77 | module.exports = groupMenus
78 |
--------------------------------------------------------------------------------
/mocker/admin/auth/index.js:
--------------------------------------------------------------------------------
1 | const groupAcl = require('./_groupAcl')
2 | const groupMenus = require('./_groupMenus')
3 | const { authenticationCheck } = require('../util')
4 |
5 | exports.authLogin = (req, res) => {
6 | const { account, password } = req.body
7 |
8 | if (!account || account.length === 0) {
9 | return res
10 | .status(422)
11 | .json([{ field: 'account', message: '请输入手机号码' }])
12 | }
13 |
14 | if (!password || password.length === 0) {
15 | return res.status(422).json([{ field: 'password', message: '请输入密码' }])
16 | }
17 |
18 | if (!/^1[3456789]\d{9}$/.test(account)) {
19 | return res
20 | .status(422)
21 | .json([{ field: 'account', message: '无效的手机号码' }])
22 | }
23 |
24 | if (account !== '13012345679') {
25 | return res
26 | .status(422)
27 | .json([{ field: 'account', message: '手机号码不存在' }])
28 | }
29 |
30 | if (password !== '123456') {
31 | return res.status(422).json([{ field: 'password', message: '密码错误' }])
32 | }
33 |
34 | return res.json({
35 | currentUser: {
36 | id: 1021,
37 | phone: '13012345678',
38 | email: '',
39 | name: '开发账号',
40 | avatar:
41 | 'https://yuncars-other.oss-cn-shanghai.aliyuncs.com//boilerplate/201903/v4md41vswd_7477uudbmg.jpg?x-oss-process=style/avatar',
42 | adminGroup: { id: 101, name: '开发组' },
43 | },
44 | accessToken: 'bmq7tDtL5GqT9b64',
45 | groupMenus: groupMenus,
46 | groupPermissions: groupAcl,
47 | })
48 | }
49 |
50 | exports.authLogout = (req, res) => {
51 | return res.json({
52 | message: '退出登录成功',
53 | })
54 | }
55 |
56 | exports.authAccount = (req, res) => {
57 | const success = {
58 | currentUser: {
59 | id: 1021,
60 | phone: '13012345678',
61 | email: '',
62 | name: '开发账号',
63 | avatar:
64 | 'https://yuncars-other.oss-cn-shanghai.aliyuncs.com//boilerplate/201903/v4md41vswd_7477uudbmg.jpg?x-oss-process=style/avatar',
65 | adminGroup: { id: 101, name: '开发组' },
66 | },
67 | groupMenus: groupMenus,
68 | groupPermissions: groupAcl,
69 | }
70 |
71 | return authenticationCheck(req, res, () => {
72 | res.json(success)
73 | })
74 | }
75 |
--------------------------------------------------------------------------------
/mocker/admin/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/lib/delay')
2 | const { authLogin, authLogout, authAccount } = require('./auth/index')
3 | const {
4 | adminList,
5 | adminItem,
6 | adminEdit,
7 | adminCreate,
8 | adminDelete,
9 | } = require('./admin/index')
10 | const {
11 | adminGroupList,
12 | adminGroupItem,
13 | adminGroupEdit,
14 | adminGroupCreate,
15 | adminGroupDelete,
16 | adminGroupOptions,
17 | adminGroupAcl,
18 | } = require('./admin-group/index')
19 | const { userList, userItem, userCreate, userDelete } = require('./user/index')
20 |
21 | const noProxy = process.env.NO_PROXY === 'true'
22 |
23 | const proxy = {
24 | _proxy: {
25 | changeHost: true,
26 | },
27 | 'POST /admin/api/auth/login': authLogin,
28 | 'POST /admin/api/auth/logout': authLogout,
29 | 'GET /admin/api/auth/account': authAccount,
30 | 'GET /admin/api/admins': adminList,
31 | 'POST /admin/api/admins': adminCreate,
32 | 'GET /admin/api/admins/:id(\\d+)': adminItem,
33 | 'PUT /admin/api/admins/:id(\\d+)': adminEdit,
34 | 'DELETE /admin/api/admins/:id(\\d+)': adminDelete,
35 | 'GET /admin/api/admin-groups': adminGroupList,
36 | 'POST /admin/api/admin-groups': adminGroupCreate,
37 | 'GET /admin/api/admin-groups/:id(\\d+)': adminGroupItem,
38 | 'PUT /admin/api/admin-groups/:id(\\d+)': adminGroupEdit,
39 | 'DELETE /admin/api/admin-groups/:id(\\d+)': adminGroupDelete,
40 | 'GET /admin/api/open/admin-group-acl': adminGroupAcl,
41 | 'GET /admin/api/open/admin-group-options': adminGroupOptions,
42 | 'GET /admin/api/users': userList,
43 | 'POST /admin/api/users': userCreate,
44 | 'GET /admin/api/users/:id(\\d+)': userItem,
45 | 'DELETE /admin/api/users/:id(\\d+)': userDelete,
46 | }
47 |
48 | module.exports = noProxy ? {} : delay(proxy, 500)
49 |
--------------------------------------------------------------------------------
/mocker/admin/user/index.js:
--------------------------------------------------------------------------------
1 | const { authenticationCheck } = require('../util')
2 | const users = require('./_users')
3 |
4 | exports.userList = (req, res) => {
5 | const { page, withSearchFields } = req.query
6 |
7 | const success = {
8 | items: users,
9 | pages: {
10 | totalCount: 1200,
11 | pageCount: 120,
12 | currentPage: Number(page),
13 | perPage: 10,
14 | },
15 | searchFields: withSearchFields
16 | ? [
17 | { type: 'keyword', field: 'phone', label: '手机号码' },
18 | { type: 'keyword', field: 'name', label: '姓名' },
19 | {
20 | type: 'select',
21 | field: 'createdFrom',
22 | label: '注册来源',
23 | multiple: true,
24 | options: {
25 | SYSTEM: '系统',
26 | APP: '应用',
27 | WEB: '网站',
28 | WECHAT: '微信',
29 | },
30 | },
31 | {
32 | type: 'dateTimeRange',
33 | rangeType: 'daterange',
34 | field: 'createdRange',
35 | labelStart: '注册开始日期',
36 | labelEnd: '注册结束日期',
37 | shortcuts: [
38 | {
39 | text: '最近一周',
40 | start: '2019-11-07',
41 | end: '2019-11-14',
42 | },
43 | {
44 | text: '最近一个月',
45 | start: '2019-10-14',
46 | end: '2019-11-14',
47 | },
48 | ],
49 | },
50 | { type: 'br' },
51 | ]
52 | : null,
53 | }
54 |
55 | return authenticationCheck(req, res, () => {
56 | return res.json(success)
57 | })
58 | }
59 |
60 | exports.userItem = (req, res) => {
61 | return res.status(404).json({
62 | message: '用户不存在',
63 | })
64 | }
65 |
66 | exports.userCreate = (req, res) => {
67 | return res.status(404).json({
68 | message: '服务不存在',
69 | })
70 | }
71 |
72 | exports.userDelete = (req, res) => {
73 | return res.status(401).json({
74 | message: '请先登录',
75 | })
76 | }
77 |
--------------------------------------------------------------------------------
/mocker/admin/util.js:
--------------------------------------------------------------------------------
1 | exports.authenticationCheck = function (req, res, success) {
2 | const clientId = req.get('X-Client-Id') || ''
3 |
4 | if (clientId === '') {
5 | return res.status(400).json({
6 | name: 'BadRequest',
7 | message: '无效的 Client Id,请刷新页面重新操作',
8 | code: 0,
9 | status: 400,
10 | })
11 | }
12 |
13 | const accessToken = req.get('X-Access-Token') || ''
14 |
15 | if (accessToken !== 'bmq7tDtL5GqT9b64') {
16 | return res.status(401).json({
17 | name: 'Unauthorized',
18 | message: '必须登陆才能进行操作',
19 | code: 0,
20 | status: 401,
21 | })
22 | }
23 |
24 | return success()
25 | }
26 |
27 | exports.notFoundCheck = function (item, message, res, success) {
28 | if (item === null) {
29 | return res.status(404).json({
30 | name: 'Not Found',
31 | message: message,
32 | code: 0,
33 | status: 404,
34 | })
35 | } else {
36 | return success()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mocker/mobile/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/lib/delay')
2 |
3 | const noProxy = process.env.NO_PROXY === 'true'
4 |
5 | const proxy = {
6 | _proxy: {
7 | changeHost: true,
8 | },
9 | 'GET /mobile/api/auth/authentication': (req, res) => {
10 | return res.json({
11 | id: 1,
12 | name: 'test',
13 | })
14 | },
15 | }
16 |
17 | module.exports = noProxy ? {} : delay(proxy, 1000)
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agile-vue",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve:admin": "cross-env MODULE_CONFIG=./vue.entry.admin.js vue-cli-service serve",
7 | "serve:mobile": "cross-env MODULE_CONFIG=./vue.entry.mobile.js vue-cli-service serve",
8 | "build:admin": "cross-env MODULE_CONFIG=./vue.entry.admin.js vue-cli-service build",
9 | "build:mobile": "cross-env MODULE_CONFIG=./vue.entry.mobile.js vue-cli-service build",
10 | "inspect:admin": "cross-env MODULE_CONFIG=./vue.entry.admin.js vue-cli-service inspect --mode production > webpack.entry.admin.js",
11 | "inspect:mobile": "cross-env MODULE_CONFIG=./vue.entry.mobile.js vue-cli-service inspect --mode production > webpack.entry.mobile.js"
12 | },
13 | "dependencies": {
14 | "@tinymce/tinymce-vue": "^3.2.8",
15 | "axios": "^0.21.0",
16 | "axios-extensions": "^3.1.3",
17 | "axios-progress-bar": "^1.2.0",
18 | "core-js": "^3.11.1",
19 | "element-ui": "^2.15.1",
20 | "perfect-scrollbar": "^1.5.0",
21 | "query-string": "^7.0.0",
22 | "register-service-worker": "^1.7.1",
23 | "vue": "^2.6.12",
24 | "vue-context": "^6.0.0",
25 | "vue-cropperjs": "^4.2.0",
26 | "vue-router": "^3.4.9",
27 | "vue-router-back-button": "^1.3.0",
28 | "vue-server-renderer": "^2.6.12",
29 | "vuex": "^3.5.1"
30 | },
31 | "devDependencies": {
32 | "@vue/cli-plugin-babel": "^4.5.12",
33 | "@vue/cli-plugin-eslint": "^4.5.12",
34 | "@vue/cli-plugin-pwa": "^4.5.12",
35 | "@vue/cli-plugin-router": "^4.5.12",
36 | "@vue/cli-plugin-vuex": "^4.5.12",
37 | "@vue/cli-service": "^4.5.12",
38 | "@vue/eslint-config-prettier": "^6.0.0",
39 | "assign-deep": "^1.0.1",
40 | "babel-eslint": "^10.1.0",
41 | "cross-env": "^7.0.2",
42 | "elliptic": ">=6.5.4",
43 | "eslint": "^7.12.1",
44 | "eslint-plugin-prettier": "^3.1.4",
45 | "eslint-plugin-vue": "^7.1.0",
46 | "koa": "^2.13.0",
47 | "less": "^4.1.1",
48 | "less-loader": "^7.3.0",
49 | "mocker-api": "^2.7.1",
50 | "prettier": "^2.1.2",
51 | "vue-template-compiler": "^2.6.12"
52 | },
53 | "eslintConfig": {
54 | "root": true,
55 | "env": {
56 | "node": true
57 | },
58 | "extends": [
59 | "plugin:vue/essential",
60 | "@vue/prettier"
61 | ],
62 | "rules": {},
63 | "parserOptions": {
64 | "parser": "babel-eslint"
65 | }
66 | },
67 | "prettier": {
68 | "tabWidth": 2,
69 | "semi": false,
70 | "singleQuote": true
71 | },
72 | "postcss": {
73 | "plugins": {
74 | "autoprefixer": {}
75 | }
76 | },
77 | "browserslist": [
78 | "> 1%",
79 | "last 2 versions"
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/public/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/common/css/spinner.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | }
5 |
6 | /* Absolute Center Spinner */
7 | #spinner {
8 | position: fixed;
9 | z-index: 999;
10 | overflow: show;
11 | margin: auto;
12 | top: 0;
13 | left: 0;
14 | bottom: 0;
15 | right: 0;
16 | width: 50px;
17 | height: 50px;
18 | }
19 |
20 | /* Transparent Overlay */
21 | #spinner:before {
22 | content: '';
23 | display: block;
24 | position: fixed;
25 | top: 0;
26 | left: 0;
27 | width: 100%;
28 | height: 100%;
29 | background-color: rgba(255, 255, 255, 0.75);
30 | }
31 |
32 | /* :not(:required) hides these rules from IE9 and below */
33 | #spinner:not(:required) {
34 | /* hide "loading..." text */
35 | font: 0/0 a;
36 | color: transparent;
37 | text-shadow: none;
38 | background-color: transparent;
39 | border: 0;
40 | }
41 |
42 | #spinner:not(:required):after {
43 | content: '';
44 | display: block;
45 | font-size: 10px;
46 | width: 50px;
47 | height: 50px;
48 | margin-top: -0.5em;
49 |
50 | border: 15px solid rgb(144, 147, 150);
51 | border-radius: 100%;
52 | border-bottom-color: transparent;
53 | -webkit-animation: spinner 1s linear 0s infinite;
54 | animation: spinner 1s linear 0s infinite;
55 | }
56 |
57 | /* Animation */
58 | @-webkit-keyframes spinner {
59 | 0% {
60 | -webkit-transform: rotate(0deg);
61 | -moz-transform: rotate(0deg);
62 | -ms-transform: rotate(0deg);
63 | -o-transform: rotate(0deg);
64 | transform: rotate(0deg);
65 | }
66 | 100% {
67 | -webkit-transform: rotate(360deg);
68 | -moz-transform: rotate(360deg);
69 | -ms-transform: rotate(360deg);
70 | -o-transform: rotate(360deg);
71 | transform: rotate(360deg);
72 | }
73 | }
74 |
75 | @-moz-keyframes spinner {
76 | 0% {
77 | -webkit-transform: rotate(0deg);
78 | -moz-transform: rotate(0deg);
79 | -ms-transform: rotate(0deg);
80 | -o-transform: rotate(0deg);
81 | transform: rotate(0deg);
82 | }
83 | 100% {
84 | -webkit-transform: rotate(360deg);
85 | -moz-transform: rotate(360deg);
86 | -ms-transform: rotate(360deg);
87 | -o-transform: rotate(360deg);
88 | transform: rotate(360deg);
89 | }
90 | }
91 |
92 | @-o-keyframes spinner {
93 | 0% {
94 | -webkit-transform: rotate(0deg);
95 | -moz-transform: rotate(0deg);
96 | -ms-transform: rotate(0deg);
97 | -o-transform: rotate(0deg);
98 | transform: rotate(0deg);
99 | }
100 | 100% {
101 | -webkit-transform: rotate(360deg);
102 | -moz-transform: rotate(360deg);
103 | -ms-transform: rotate(360deg);
104 | -o-transform: rotate(360deg);
105 | transform: rotate(360deg);
106 | }
107 | }
108 |
109 | @keyframes spinner {
110 | 0% {
111 | -webkit-transform: rotate(0deg);
112 | -moz-transform: rotate(0deg);
113 | -ms-transform: rotate(0deg);
114 | -o-transform: rotate(0deg);
115 | transform: rotate(0deg);
116 | }
117 | 100% {
118 | -webkit-transform: rotate(360deg);
119 | -moz-transform: rotate(360deg);
120 | -ms-transform: rotate(360deg);
121 | -o-transform: rotate(360deg);
122 | transform: rotate(360deg);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/public/common/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/favicon.ico
--------------------------------------------------------------------------------
/public/common/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/common/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/common/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/common/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/common/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/common/icons/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/mstile-144x144.png
--------------------------------------------------------------------------------
/public/common/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/public/common/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/public/common/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
30 |
--------------------------------------------------------------------------------
/public/mobile/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/website/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/core/assets/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/src/core/assets/images/avatar.png
--------------------------------------------------------------------------------
/src/core/assets/styles/base.less:
--------------------------------------------------------------------------------
1 | html {
2 | position: relative;
3 | min-height: 100%;
4 | background-color: #fff;
5 | }
6 |
7 | body {
8 | -webkit-font-smoothing: antialiased;
9 | background: #fff;
10 | margin: 0;
11 | overflow-x: hidden;
12 | color: #67757c;
13 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
14 | Microsoft YaHei, SimSun, sans-serif;
15 | }
16 |
17 | a:focus,
18 | a:hover {
19 | text-decoration: none;
20 | }
21 |
22 | svg {
23 | pointer-events: none;
24 | }
25 |
26 | .uppercase,
27 | .uppercase input {
28 | text-transform: uppercase;
29 | }
30 |
31 | .cursor-pointer {
32 | cursor: pointer;
33 | }
34 |
35 | .vertical-center {
36 | vertical-align: middle !important;
37 | white-space: nowrap;
38 | }
39 |
40 | .vertical-bottom {
41 | vertical-align: bottom !important;
42 | white-space: nowrap;
43 | }
44 |
45 | .text-mono {
46 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
47 | 'Courier New', monospace;
48 | }
49 |
50 | .text-nowrap {
51 | white-space: nowrap;
52 | }
53 |
54 | .text-delete {
55 | text-decoration: line-through;
56 | }
57 |
58 | .text-right {
59 | text-align: right;
60 | }
61 |
62 | .color-red {
63 | color: #f00;
64 | }
65 |
66 | hr.spacer-lg,
67 | hr.spacer-md,
68 | hr.spacer-sm,
69 | hr.spacer-xl,
70 | hr.spacer-xs {
71 | border: medium none;
72 | margin-bottom: 0;
73 | margin-top: 0;
74 | }
75 |
76 | hr.spacer-lg {
77 | height: 50px;
78 | }
79 |
80 | hr.spacer-md {
81 | height: 39px;
82 | }
83 |
84 | hr.spacer-sm {
85 | height: 30px;
86 | }
87 |
88 | hr.spacer-xl {
89 | height: 20px;
90 | }
91 |
92 | hr.spacer-xs {
93 | height: 10px;
94 | }
95 |
96 | ::-webkit-scrollbar {
97 | width: 9px;
98 | height: 9px;
99 | }
100 |
101 | ::-webkit-scrollbar-track {
102 | border-radius: 3px;
103 | background: rgba(0, 0, 0, 0.06);
104 | -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.08);
105 | }
106 |
107 | ::-webkit-scrollbar-thumb {
108 | border-radius: 3px;
109 | background: rgba(0, 0, 0, 0.12);
110 | -webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2);
111 | }
112 |
--------------------------------------------------------------------------------
/src/core/assets/styles/mixin.less:
--------------------------------------------------------------------------------
1 | .clearfix {
2 | &::after {
3 | display: block;
4 | content: '';
5 | clear: both;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/core/components/Avatar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
38 |
39 |
53 |
--------------------------------------------------------------------------------
/src/core/components/BlankLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/core/components/Icon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
24 |
44 |
--------------------------------------------------------------------------------
/src/core/components/Placeholder/PlaceholderForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
43 |
44 |
45 |
84 |
85 |
95 |
--------------------------------------------------------------------------------
/src/core/components/Placeholder/PlaceholderTextBlock.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
48 |
49 |
54 |
--------------------------------------------------------------------------------
/src/core/components/Placeholder/PlaceholderTextRow.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
38 |
39 |
47 |
--------------------------------------------------------------------------------
/src/core/components/Placeholder/animation.less:
--------------------------------------------------------------------------------
1 | .placeholder-animation {
2 | &.placeholder-text-row,
3 | .placeholder-text-row {
4 | animation: placeholder-pulse 1s infinite ease-in-out;
5 | }
6 | }
7 |
8 | @keyframes placeholder-pulse {
9 | 0% {
10 | opacity: 0.3;
11 | }
12 |
13 | 50% {
14 | opacity: 0.9;
15 | }
16 |
17 | 100% {
18 | opacity: 0.3;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/core/components/PrefectScrollbar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
19 |
55 |
56 |
105 |
--------------------------------------------------------------------------------
/src/core/components/Tinymce/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
38 |
39 |
40 |
101 |
--------------------------------------------------------------------------------
/src/core/directives/SameWidth.js:
--------------------------------------------------------------------------------
1 | const childrenSameWidth = function (el, childClassName) {
2 | if (el.dataset.sameWidth === 'DONE') {
3 | return
4 | }
5 |
6 | const items = el.getElementsByClassName(childClassName)
7 |
8 | if (items.length < 2) {
9 | return
10 | }
11 |
12 | let maxWidth = 0
13 |
14 | for (const item of items) {
15 | const width = item.offsetWidth
16 |
17 | if (width > maxWidth) {
18 | maxWidth = width
19 | }
20 | }
21 |
22 | for (const item of items) {
23 | item.style.width = maxWidth + 2 + 'px'
24 | }
25 |
26 | el.dataset.sameWidth = 'DONE'
27 | }
28 |
29 | const SameWidth = {
30 | inserted: function (el, binding) {
31 | childrenSameWidth(el, binding.value)
32 | },
33 | componentUpdated: function (el, binding) {
34 | childrenSameWidth(el, binding.value)
35 | },
36 | }
37 |
38 | export default SameWidth
39 |
--------------------------------------------------------------------------------
/src/core/directives/TransformDom.js:
--------------------------------------------------------------------------------
1 | const TransformDom = {
2 | inserted: function (el, bindings) {
3 | const container = bindings.arg
4 | ? document.getElementById(bindings.arg)
5 | : document.body
6 |
7 | if (container) {
8 | bindings.modifiers.prepend && container.firstChild
9 | ? container.insertBefore(el, container.firstChild)
10 | : container.appendChild(el)
11 | }
12 | },
13 |
14 | unbind: function (el) {
15 | if (el.parentNode) {
16 | el.parentNode.removeChild(el)
17 | }
18 | },
19 | }
20 |
21 | export default TransformDom
22 |
--------------------------------------------------------------------------------
/src/core/plugins/HttpClient.js:
--------------------------------------------------------------------------------
1 | import Request from '../utils/request'
2 |
3 | const UnauthorizedHttpCode = 401
4 | const UnprocessableEntityHttpCode = 422
5 |
6 | const HttpGetMethod = ['GET', 'HEAD']
7 |
8 | const HttpClient = {
9 | install(
10 | Vue,
11 | {
12 | getApiHost,
13 | getAccessToken,
14 | setLoginAction,
15 | setErrorMessage,
16 | paramsSerializer = null,
17 | }
18 | ) {
19 | const request = new Request({
20 | baseUrl: getApiHost(),
21 | paramsSerializer: paramsSerializer,
22 | beforeRequest: (config) => {
23 | const accessToken = getAccessToken()
24 |
25 | if (accessToken) {
26 | config.headers['X-Client-Id'] = accessToken.clientId
27 | config.headers['X-Access-Token'] = accessToken.accessToken
28 | }
29 |
30 | return config
31 | },
32 | onSuccess: (response) => {
33 | if (response.config.responseType === 'blob') {
34 | return Promise.resolve(response)
35 | }
36 |
37 | return Promise.resolve(response.data)
38 | },
39 | onError: (error) => {
40 | const historyBack = error.config.historyBack
41 |
42 | if (!error.response) {
43 | setErrorMessage(error.message, historyBack)
44 |
45 | return Promise.reject(error)
46 | }
47 |
48 | if (error.response.status === UnauthorizedHttpCode) {
49 | if (!error.config.__storeDispatch) {
50 | error.config.__storeDispatch = true
51 |
52 | if (
53 | historyBack ||
54 | HttpGetMethod.includes(error.config.method.toUpperCase())
55 | ) {
56 | setLoginAction('direct')
57 | } else {
58 | setLoginAction('modal')
59 | }
60 | }
61 |
62 | return Promise.reject(error)
63 | }
64 |
65 | if (error.response.status === UnprocessableEntityHttpCode) {
66 | return Promise.reject(error)
67 | }
68 |
69 | setErrorMessage(
70 | error.response.data.detail ||
71 | error.response.data.message ||
72 | error.response.data.title ||
73 | error.response.statusText ||
74 | '网络请求错误',
75 | historyBack
76 | )
77 |
78 | return Promise.reject(error)
79 | },
80 | })
81 |
82 | Vue.http = request
83 | Vue.prototype.$http = request
84 | },
85 | }
86 |
87 | export default HttpClient
88 |
--------------------------------------------------------------------------------
/src/core/utils/flatry.js:
--------------------------------------------------------------------------------
1 | const flatry = (promise) =>
2 | promise
3 | .then((data) => ({ data, error: null }))
4 | .catch((error) => ({ error, data: null }))
5 |
6 | export default flatry
7 |
--------------------------------------------------------------------------------
/src/core/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { loadProgressBar } from 'axios-progress-bar'
3 | import 'axios-progress-bar/dist/nprogress.css'
4 | import { throttleAdapterEnhancer } from 'axios-extensions'
5 | import flatry from '@core/utils/flatry'
6 |
7 | class Request {
8 | constructor(options) {
9 | const opt = {
10 | ...{
11 | baseUrl: '',
12 | timeout: 10 * 1000,
13 | withCredentials: false,
14 | paramsSerializer: null,
15 | beforeRequest: (config) => {
16 | return config
17 | },
18 | onSuccess: (response) => Promise.resolve(response),
19 | onError: (error) => Promise.reject(error),
20 | },
21 | ...options,
22 | }
23 |
24 | const axiosConfig = {
25 | baseURL: opt.baseUrl,
26 | timeout: opt.timeout,
27 | withCredentials: opt.withCredentials,
28 | paramsSerializer: opt.paramsSerializer,
29 | adapter: throttleAdapterEnhancer(axios.defaults.adapter),
30 | }
31 |
32 | const httpClient = axios.create(axiosConfig)
33 |
34 | loadProgressBar({ showSpinner: false }, httpClient)
35 |
36 | httpClient.interceptors.request.use(
37 | (config) => {
38 | return opt.beforeRequest(config)
39 | },
40 | (error) => {
41 | return opt.onError(error)
42 | }
43 | )
44 |
45 | httpClient.interceptors.response.use(
46 | (response) => {
47 | return opt.onSuccess(response)
48 | },
49 | (error) => {
50 | return opt.onError(error)
51 | }
52 | )
53 |
54 | this.httpClient = httpClient
55 | }
56 |
57 | request(
58 | method,
59 | url,
60 | params = null,
61 | data = null,
62 | historyBack = false,
63 | cancelIgnore = false
64 | ) {
65 | const config = {
66 | url: url,
67 | method: method,
68 | historyBack: historyBack,
69 | cancelIgnore: cancelIgnore,
70 | }
71 |
72 | if (params) {
73 | config.params = params
74 | }
75 |
76 | if (data) {
77 | config.data = data
78 | }
79 |
80 | return flatry(this.httpClient.request(config))
81 | }
82 |
83 | get(url, params = null, historyBack = true, cancelIgnore = false) {
84 | return this.request('GET', url, params, null, historyBack, cancelIgnore)
85 | }
86 |
87 | post(url, data = null, params = null, historyBack = false) {
88 | return this.request('POST', url, params, data, historyBack)
89 | }
90 |
91 | put(url, data = null, params = null, historyBack = false) {
92 | return this.request('PUT', url, params, data, historyBack)
93 | }
94 |
95 | path(url, data = null, params = null, historyBack = false) {
96 | return this.request('PATH', url, params, data, historyBack)
97 | }
98 |
99 | delete(url, params = null, historyBack = false) {
100 | return this.request('DELETE', url, params, null, historyBack)
101 | }
102 |
103 | download(method, url, params = null, data = null, historyBack = false) {
104 | const config = {
105 | url: url,
106 | method: method,
107 | timeout: 120 * 1000,
108 | responseType: 'blob',
109 | historyBack: historyBack,
110 | onDownloadProgress: (progressEvent) => {
111 | console.log(progressEvent)
112 | },
113 | }
114 |
115 | if (params) {
116 | config.params = params
117 | }
118 |
119 | if (data) {
120 | config.data = data
121 | }
122 |
123 | return flatry(
124 | this.httpClient.request(config).then((response) => {
125 | let filename = response.headers['x-suggested-filename']
126 |
127 | if (!filename) {
128 | filename = response.headers['content-disposition'].match(
129 | /filename="(.+)"/
130 | )[1]
131 | }
132 |
133 | if (filename) {
134 | const url = window.URL.createObjectURL(
135 | new Blob([response.data], {
136 | type: response.headers['content-type'],
137 | })
138 | )
139 | const link = document.createElement('a')
140 | link.href = url
141 | link.setAttribute('download', decodeURIComponent(filename))
142 | link.click()
143 | window.URL.revokeObjectURL(url)
144 |
145 | return true
146 | } else {
147 | return false
148 | }
149 | })
150 | )
151 | }
152 | }
153 |
154 | export default Request
155 |
--------------------------------------------------------------------------------
/src/core/utils/upload.js:
--------------------------------------------------------------------------------
1 | import Request from '@core/utils/request'
2 |
3 | const Upload = {
4 | defaultOption: () => {
5 | return {
6 | url: '',
7 | cropUrl: '',
8 | timeout: 0,
9 | params: [],
10 | headers: [],
11 | dataType: '',
12 | paramName: '',
13 | imageProcess: '',
14 | responseParse: '',
15 | sizeLimit: 0,
16 | typesLimit: [],
17 | }
18 | },
19 | upload: (url, option, file, onSuccess, onError) => {
20 | const request = new Request({
21 | onSuccess: (response) => {
22 | const result =
23 | option.dataType === 'xml'
24 | ? new DOMParser().parseFromString(response.data, 'application/xml')
25 | : response.data
26 |
27 | // eslint-disable-next-line no-new-func
28 | const responseParse = new Function('result', option.responseParse)
29 |
30 | const upload = responseParse(result)
31 |
32 | onSuccess(upload)
33 | },
34 | onError: (error) => {
35 | const message =
36 | error.response.data.detail ||
37 | error.response.data.title ||
38 | error.response.data.message ||
39 | error.response.statusText ||
40 | error.message
41 |
42 | onError(message)
43 | },
44 | })
45 |
46 | const headers = option.headers
47 |
48 | if (headers && Array.isArray(headers)) {
49 | request.httpClient.interceptors.request.use((config) => {
50 | for (const key in headers) {
51 | if (!headers.hasOwnProperty(key)) {
52 | continue
53 | }
54 |
55 | config.headers[key] = headers[key]
56 | }
57 |
58 | return config
59 | }, undefined)
60 | }
61 |
62 | const params = option.params
63 |
64 | const formData = new FormData()
65 |
66 | if (params) {
67 | for (const key in params) {
68 | if (!params.hasOwnProperty(key)) {
69 | continue
70 | }
71 |
72 | const value = params[key]
73 |
74 | // eslint-disable-next-line no-template-curly-in-string
75 | if (value.toString().indexOf('${filename}') !== -1) {
76 | let randomFileName =
77 | Math.random().toString(36).substring(3, 15) +
78 | '.' +
79 | file.name.split('.').pop()
80 |
81 | // eslint-disable-next-line no-template-curly-in-string
82 | formData.append(
83 | key,
84 | value.toString().replace('${filename}', randomFileName)
85 | )
86 | } else {
87 | formData.append(key, value)
88 | }
89 | }
90 | }
91 |
92 | formData.append(option.paramName, file, file.name)
93 |
94 | request.post(url, formData, null, false)
95 | },
96 | }
97 |
98 | export default Upload
99 |
--------------------------------------------------------------------------------
/src/core/utils/util.js:
--------------------------------------------------------------------------------
1 | export const deepSearch = (needle, haystack, found = []) => {
2 | Object.keys(haystack).forEach((key) => {
3 | if (!haystack[key]) {
4 | return
5 | }
6 |
7 | if (key === needle) {
8 | found.push(haystack[key])
9 | return found
10 | }
11 | if (typeof haystack[key] === 'object') {
12 | return deepSearch(needle, haystack[key], found)
13 | }
14 | })
15 |
16 | return found
17 | }
18 |
19 | export const formatUrl = (url) => {
20 | if (url === 'site/index') {
21 | return 'home'
22 | }
23 |
24 | if (url.endsWith('/index')) {
25 | return url.substr(0, url.length - 6)
26 | }
27 |
28 | return url
29 | }
30 |
31 | export const tabledObject = (object, options = []) => {
32 | const result = []
33 |
34 | Object.entries(object).forEach(([key, value]) => {
35 | const option = options.find((option) => {
36 | return option.property === key
37 | })
38 |
39 | if (option) {
40 | if (option.callback) {
41 | result.push({ name: option.name, value: option.callback(value) })
42 | } else {
43 | result.push({ name: option.name, value: value })
44 | }
45 | } else {
46 | result.push({ name: key, value: value })
47 | }
48 | })
49 |
50 | return result
51 | }
52 |
--------------------------------------------------------------------------------
/src/modules/admin/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
154 |
155 |
159 |
--------------------------------------------------------------------------------
/src/modules/admin/assets/images/banner-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/src/modules/admin/assets/images/banner-white.png
--------------------------------------------------------------------------------
/src/modules/admin/assets/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/src/modules/admin/assets/images/banner.png
--------------------------------------------------------------------------------
/src/modules/admin/assets/images/login-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/src/modules/admin/assets/images/login-bg.jpg
--------------------------------------------------------------------------------
/src/modules/admin/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/src/modules/admin/assets/images/logo.png
--------------------------------------------------------------------------------
/src/modules/admin/assets/styles/default-layout.less:
--------------------------------------------------------------------------------
1 | @media (min-width: 992px) {
2 | .aside-fixed {
3 | .ag-aside,
4 | .ag-logo {
5 | width: 200px !important;
6 | }
7 | .ag-main {
8 | margin-left: 200px;
9 | }
10 | }
11 |
12 | .aside-icon {
13 | .ag-aside,
14 | .ag-logo {
15 | width: 60px !important;
16 | }
17 |
18 | .ag-main {
19 | margin-left: 60px;
20 | }
21 | }
22 | }
23 |
24 | .ag-layout {
25 | background: #f4f8fb;
26 | min-height: 100vh;
27 |
28 | .ag-header {
29 | position: fixed;
30 | width: 100%;
31 | z-index: 1030;
32 | padding: 0 0 20px 0;
33 | background: #fff;
34 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.02);
35 | }
36 |
37 | .ag-wrap {
38 | padding-top: 50px;
39 | }
40 |
41 | @media screen and (max-width: 491px) {
42 | .ag-logo {
43 | width: 50px !important;
44 | }
45 | }
46 |
47 | @media screen and (max-width: 991px) {
48 | .ag-logo {
49 | width: 200px;
50 | }
51 | .ag-aside {
52 | top: 50px;
53 | bottom: 0;
54 | right: -200px;
55 | width: 200px !important;
56 | z-index: 19811223;
57 | }
58 | .ag-aside.active {
59 | right: 0;
60 | }
61 | }
62 |
63 | .ag-aside {
64 | position: fixed;
65 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.01);
66 | transition: width 0.2s;
67 |
68 | .ag-scrollbar {
69 | height: calc(100vh - 50px);
70 | }
71 |
72 | .ag-menu {
73 | min-height: calc(100vh - 50px);
74 | }
75 | }
76 |
77 | .ag-main {
78 | padding: 16px 20px;
79 | }
80 |
81 | .ag-content {
82 | .box {
83 | background: #fff;
84 | padding: 20px;
85 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.02);
86 |
87 | .box-header {
88 | border-bottom: 1px solid #ddd;
89 | margin-bottom: 20px;
90 | padding-bottom: 13px;
91 |
92 | h4 {
93 | font-size: 14px;
94 | font-weight: normal;
95 | }
96 |
97 | h1,
98 | h2,
99 | h3,
100 | h4,
101 | h5,
102 | h6 {
103 | margin: 0;
104 | }
105 | }
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/modules/admin/components/Breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 管理后台
6 |
7 |
12 |
13 | {{ breadcrumb.title }}
14 |
15 |
16 |
17 |
18 |
46 |
47 |
56 |
--------------------------------------------------------------------------------
/src/modules/admin/components/DistrictCascader.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
82 |
--------------------------------------------------------------------------------
/src/modules/admin/components/ExportButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
66 |
--------------------------------------------------------------------------------
/src/modules/admin/components/ImageCropper.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
27 |
33 |
34 |
35 |
36 |
115 |
116 |
129 |
--------------------------------------------------------------------------------
/src/modules/admin/components/LoginModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/src/modules/admin/components/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
44 |
50 |
--------------------------------------------------------------------------------
/src/modules/admin/components/RemoteSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/modules/admin/components/SiderMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
24 |
25 |
26 |
27 |
28 |
73 |
127 |
--------------------------------------------------------------------------------
/src/modules/admin/components/SiderMenuIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/src/modules/admin/components/SiderMenuItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ menu.label }}
5 |
6 |
7 |
8 |
28 |
--------------------------------------------------------------------------------
/src/modules/admin/components/SiderMenuSub.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | {{ menu.label }}
11 |
12 |
13 |
18 |
25 |
26 |
27 |
28 |
29 |
62 |
--------------------------------------------------------------------------------
/src/modules/admin/components/upload/AvatarUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
35 |
--------------------------------------------------------------------------------
/src/modules/admin/components/upload/FileUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
42 |
--------------------------------------------------------------------------------
/src/modules/admin/components/upload/ImageUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
75 |
--------------------------------------------------------------------------------
/src/modules/admin/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import queryString from 'query-string'
3 |
4 | import ElementUI from 'element-ui'
5 | import 'element-ui/lib/theme-chalk/index.css'
6 |
7 | import router from './router'
8 | import store from './store'
9 |
10 | import App from './App.vue'
11 |
12 | import './registerServiceWorker'
13 |
14 | import HttpClient from '@core/plugins/HttpClient'
15 | import DeleteDialog from './plugins/DeleteDialog'
16 | import PermissionCheck from './plugins/PermissionCheck'
17 |
18 | Vue.use(ElementUI)
19 |
20 | Vue.use(HttpClient, {
21 | getApiHost: () => {
22 | return document
23 | .querySelector('meta[name="api-host"]')
24 | .getAttribute('content')
25 | },
26 | getAccessToken: () => {
27 | return store.getters['auth/getAccessToken']
28 | },
29 | setLoginAction: async (action) => {
30 | await store.dispatch('auth/setLoginAction', action)
31 | },
32 | setErrorMessage: async (message, historyBack) => {
33 | await store.dispatch('setError', {
34 | message: message,
35 | historyBack: historyBack,
36 | })
37 | },
38 | paramsSerializer: function (params) {
39 | return queryString.stringify(params, {
40 | arrayFormat: process.env.VUE_APP_QS_ARRAY_FORMAT || 'bracket',
41 | })
42 | },
43 | })
44 |
45 | Vue.use(PermissionCheck, {
46 | store,
47 | })
48 |
49 | Vue.use(DeleteDialog, {
50 | MessageBox: ElementUI.MessageBox,
51 | })
52 |
53 | Vue.config.productionTip = false
54 |
55 | Vue.mixin({
56 | data: function () {
57 | return {
58 | tinymceUrl: process.env.VUE_APP_TINYMCE_URL,
59 | tinymceLanguageUrl: process.env.VUE_APP_TINYMCE_LANGUAGE_URL,
60 | }
61 | },
62 | })
63 |
64 | new Vue({
65 | router,
66 | store,
67 | render: (h) => h(App),
68 | }).$mount('#root')
69 |
--------------------------------------------------------------------------------
/src/modules/admin/mixins/SearchFormFieldsMixin.js:
--------------------------------------------------------------------------------
1 | const SearchFormFieldsMixin = {
2 | data() {
3 | return {
4 | searchFields: false,
5 | }
6 | },
7 | methods: {
8 | buildRouteQuery(query) {
9 | return this.searchFields !== false
10 | ? query
11 | : Object.assign({}, query, { withSearchFields: true })
12 | },
13 | setSearchFields(searchFields) {
14 | if (searchFields) {
15 | this.searchFields = searchFields || []
16 | }
17 | },
18 | },
19 | }
20 |
21 | export default SearchFormFieldsMixin
22 |
--------------------------------------------------------------------------------
/src/modules/admin/mixins/UnprocessableEntityHttpErrorMixin.js:
--------------------------------------------------------------------------------
1 | const UnprocessableEntityHttpErrorMixin = {
2 | data() {
3 | return {
4 | violations: [],
5 | }
6 | },
7 | methods: {
8 | clearViolationError(formName) {
9 | this.$refs[formName].$children.forEach((child) => {
10 | child.$data.validateMessage = ''
11 | child.$data.validateState = 'success'
12 | })
13 | },
14 | handleViolationError(error, formName) {
15 | if (
16 | !error ||
17 | !error.response ||
18 | !error.response.status ||
19 | error.response.status !== 422
20 | ) {
21 | return
22 | }
23 |
24 | if (Array.isArray(error.response.data.violations)) {
25 | this.violations = error.response.data.violations
26 | }
27 |
28 | if (Array.isArray(error.response.data)) {
29 | this.violations = error.response.data
30 | }
31 |
32 | if (this.violations.length === 0) {
33 | return
34 | }
35 |
36 | this.$refs[formName].$children.forEach((child) => {
37 | if (child.prop) {
38 | const childProp = child.prop && child.prop.split('.')[0]
39 |
40 | const activeViolation = this.violations.find((violation) => {
41 | const violationFieldPath = violation.field.split('.')
42 |
43 | return childProp === violationFieldPath.pop()
44 | })
45 |
46 | if (activeViolation) {
47 | child.$data.validateMessage = activeViolation.message
48 | child.$data.validateState = activeViolation.message
49 | ? 'error'
50 | : 'success'
51 | } else {
52 | child.$data.validateMessage = ''
53 | child.$data.validateState = 'success'
54 | }
55 | }
56 | })
57 | },
58 | },
59 | }
60 |
61 | export default UnprocessableEntityHttpErrorMixin
62 |
--------------------------------------------------------------------------------
/src/modules/admin/plugins/DeleteDialog.js:
--------------------------------------------------------------------------------
1 | const DeleteDialog = {
2 | install(Vue, { MessageBox }) {
3 | Vue.prototype.$deleteDialog = (option) => {
4 | const dialogOption = Object.assign(
5 | {
6 | title: '你确定吗?',
7 | message: '记录将被删除',
8 | callback: null,
9 | promptLabel: '',
10 | promptValue: '',
11 | },
12 | option
13 | )
14 |
15 | if (dialogOption.promptValue.length > 0) {
16 | MessageBox.prompt(
17 | '' +
18 | dialogOption.title +
19 | '
' +
20 | dialogOption.message +
21 | '
',
22 | {
23 | showClose: false,
24 | confirmButtonText: '确定',
25 | cancelButtonText: '取消',
26 | iconClass: 'el-icon-warning-outline',
27 | customClass: 'delete-dialog',
28 | dangerouslyUseHTMLString: true,
29 | center: false,
30 | callback: (action) => {
31 | if (action === 'confirm') {
32 | dialogOption.callback()
33 | }
34 | },
35 | inputPlaceholder: '' + dialogOption.promptLabel,
36 | inputValidator: (value) => {
37 | return dialogOption.promptValue === value
38 | },
39 | inputErrorMessage: dialogOption.promptLabel + '不匹配',
40 | }
41 | )
42 | } else {
43 | MessageBox.confirm(
44 | '' +
45 | dialogOption.title +
46 | '
' +
47 | dialogOption.message +
48 | '
',
49 | {
50 | showClose: false,
51 | confirmButtonText: '确定',
52 | cancelButtonText: '取消',
53 | iconClass: 'el-icon-warning-outline',
54 | customClass: 'delete-dialog',
55 | dangerouslyUseHTMLString: true,
56 | center: false,
57 | callback: (action) => {
58 | if (action === 'confirm') {
59 | dialogOption.callback()
60 | }
61 | },
62 | }
63 | )
64 | }
65 | }
66 | },
67 | }
68 |
69 | export default DeleteDialog
70 |
--------------------------------------------------------------------------------
/src/modules/admin/plugins/PermissionCheck.js:
--------------------------------------------------------------------------------
1 | const PermissionCheck = {
2 | install(Vue, { store }) {
3 | Vue.prototype.$can = (actionId) => {
4 | return store.getters['auth/isRouteInAcl'](actionId)
5 | }
6 | },
7 | }
8 |
9 | module.exports = PermissionCheck
10 |
--------------------------------------------------------------------------------
/src/modules/admin/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { register } from 'register-service-worker'
4 |
5 | if (process.env.NODE_ENV === 'production') {
6 | register(`${process.env.BASE_URL}service-worker.js`, {
7 | ready() {
8 | console.log(
9 | 'App is being served from cache by a service worker.\n' +
10 | 'For more details, visit https://goo.gl/AFskqB'
11 | )
12 | },
13 | registered() {
14 | console.log('Service worker has been registered.')
15 | },
16 | cached() {
17 | console.log('Content has been cached for offline use.')
18 | },
19 | updatefound() {
20 | console.log('New content is downloading.')
21 | },
22 | updated() {
23 | console.log('New content is available; please refresh.')
24 | },
25 | offline() {
26 | console.log(
27 | 'No internet connection found. App is running in offline mode.'
28 | )
29 | },
30 | error(error) {
31 | console.error('Error during service worker registration:', error)
32 | },
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/src/modules/admin/router/admin/admin-log.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/admin-log',
4 | name: 'AdminLog',
5 | component: () =>
6 | import(
7 | /* webpackChunkName: "chunk-admin" */ '@admin/views/admin-log/Index'
8 | ),
9 | meta: {
10 | title: '操作日志',
11 | },
12 | },
13 | ]
14 |
--------------------------------------------------------------------------------
/src/modules/admin/router/admin/admin.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/admin',
4 | name: 'Admin',
5 | component: () =>
6 | import(/* webpackChunkName: "chunk-admin" */ '@admin/views/admin/Index'),
7 | meta: {
8 | title: '管理员',
9 | },
10 | },
11 | {
12 | path: '/admin/create',
13 | name: 'AdminCreate',
14 | component: () =>
15 | import(/* webpackChunkName: "chunk-admin" */ '@admin/views/admin/Create'),
16 | meta: {
17 | title: '新建管理员',
18 | },
19 | },
20 | {
21 | path: '/admin/edit/:id',
22 | name: 'AdminEdit',
23 | component: () =>
24 | import(/* webpackChunkName: "chunk-admin" */ '@admin/views/admin/Edit'),
25 | meta: {
26 | title: '编辑管理员',
27 | },
28 | },
29 | ]
30 |
--------------------------------------------------------------------------------
/src/modules/admin/router/admin/group.js:
--------------------------------------------------------------------------------
1 | import AdminLayout from '@admin/components/AdminLayout'
2 |
3 | export default [
4 | {
5 | path: '/admin-group',
6 | name: 'AdminGroup',
7 | component: () =>
8 | import(
9 | /* webpackChunkName: "chunk-admin" */ '@admin/views/admin-group/Index'
10 | ),
11 | meta: {
12 | title: '管理组',
13 | },
14 | },
15 | {
16 | path: '/admin-group/create',
17 | name: 'AdminGroupCreate',
18 | component: () =>
19 | import(
20 | /* webpackChunkName: "chunk-admin" */ '@admin/views/admin-group/Create'
21 | ),
22 | meta: {
23 | title: '新建管理组',
24 | },
25 | },
26 | {
27 | path: '/admin-group/edit/:id',
28 | name: 'AdminGroupEdit',
29 | component: () =>
30 | import(
31 | /* webpackChunkName: "chunk-admin" */ '@admin/views/admin-group/Edit'
32 | ),
33 | meta: {
34 | title: '编辑管理组',
35 | },
36 | },
37 | ]
38 |
--------------------------------------------------------------------------------
/src/modules/admin/router/cms/article.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/cms-article',
4 | name: 'CmsArticle',
5 | component: () =>
6 | import(
7 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-article/Index'
8 | ),
9 | meta: {
10 | title: '文章管理',
11 | },
12 | },
13 | {
14 | path: '/cms-article/create',
15 | name: 'CmsArticleCreate',
16 | component: () =>
17 | import(
18 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-article/Create'
19 | ),
20 | meta: {
21 | title: '新建文章',
22 | },
23 | },
24 | {
25 | path: '/cms-article/edit/:id',
26 | name: 'CmsArticleEdit',
27 | component: () =>
28 | import(
29 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-article/Edit'
30 | ),
31 | meta: {
32 | title: '编辑文章',
33 | },
34 | },
35 | ]
36 |
--------------------------------------------------------------------------------
/src/modules/admin/router/cms/category.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/cms-category',
4 | component: () =>
5 | import(
6 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-category/Layout'
7 | ),
8 | children: [
9 | {
10 | path: '',
11 | name: 'CmsCategory',
12 | component: () =>
13 | import(
14 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-category/Index'
15 | ),
16 | meta: {
17 | title: '内容分类',
18 | },
19 | },
20 | {
21 | path: 'create/:id',
22 | name: 'CmsCategoryCreate',
23 | component: () =>
24 | import(
25 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-category/Create'
26 | ),
27 | meta: {
28 | title: '新建内容分类',
29 | parent: {
30 | name: 'CmsCategory',
31 | path: '/cms-category',
32 | title: '内容分类',
33 | },
34 | },
35 | },
36 | {
37 | path: 'edit/:id',
38 | name: 'CmsCategoryEdit',
39 | component: () =>
40 | import(
41 | /* webpackChunkName: "chunk-cms" */ '@admin/views/cms-category/Edit'
42 | ),
43 | meta: {
44 | title: '编辑内容分类',
45 | parent: {
46 | name: 'CmsCategory',
47 | path: '/cms-category',
48 | title: '内容分类',
49 | },
50 | },
51 | },
52 | ],
53 | },
54 | ]
55 |
--------------------------------------------------------------------------------
/src/modules/admin/router/district/district.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/district',
4 | component: () =>
5 | import(
6 | /* webpackChunkName: "chunk-district" */ '@admin/views/district/Layout'
7 | ),
8 | children: [
9 | {
10 | path: '',
11 | name: 'District',
12 | component: () =>
13 | import(
14 | /* webpackChunkName: "chunk-district" */ '@admin/views/district/Index'
15 | ),
16 | meta: {
17 | title: '地区',
18 | },
19 | },
20 | {
21 | path: 'create/:id',
22 | name: 'DistrictCreate',
23 | component: () =>
24 | import(
25 | /* webpackChunkName: "chunk-district" */ '@admin/views/district/Create'
26 | ),
27 | meta: {
28 | title: '新建地区',
29 | parent: {
30 | name: 'District',
31 | path: '/district',
32 | title: '地区',
33 | },
34 | },
35 | },
36 | {
37 | path: 'edit/:id',
38 | name: 'DistrictEdit',
39 | component: () =>
40 | import(
41 | /* webpackChunkName: "chunk-district" */ '@admin/views/district/Edit'
42 | ),
43 | meta: {
44 | title: '编辑地区',
45 | parent: {
46 | name: 'District',
47 | path: '/district',
48 | title: '地区',
49 | },
50 | },
51 | },
52 | ],
53 | },
54 | ]
55 |
--------------------------------------------------------------------------------
/src/modules/admin/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import VueRouterBackButton from 'vue-router-back-button'
4 |
5 | import notFound from '@admin/views/site/NotFound'
6 | import siteIndex from '@admin/views/site/Index'
7 | import siteLogin from '@admin/views/site/Login'
8 | import siteProfile from '@admin/views/site/Profile'
9 |
10 | import adminRoute from './admin/admin'
11 | import adminLogRoute from './admin/admin-log'
12 | import adminGroupRoute from './admin/group'
13 |
14 | import userRoute from './user/user'
15 | import userAddressRoute from './user/address'
16 |
17 | import shopCategoryRoute from './shop/category'
18 | import shopBrandRoute from './shop/brand'
19 | import shopProductRoute from './shop/product'
20 |
21 | import districtRoute from './district/district'
22 |
23 | import cmsCategoryRoute from './cms/category'
24 | import cmsArticleRoute from './cms/article'
25 |
26 | import AdminLayout from '@admin/components/AdminLayout'
27 |
28 | Vue.use(VueRouter)
29 |
30 | const originalPush = VueRouter.prototype.push
31 | VueRouter.prototype.push = function push(location, onResolve, onReject) {
32 | if (onResolve || onReject) {
33 | return originalPush.call(this, location, onResolve, onReject)
34 | }
35 |
36 | return originalPush.call(this, location).catch((err) => err)
37 | }
38 |
39 | const routes = [
40 | {
41 | path: '/login',
42 | name: 'Login',
43 | component: siteLogin,
44 | meta: {
45 | title: '登录',
46 | },
47 | },
48 | {
49 | path: '/',
50 | component: AdminLayout,
51 | children: [
52 | {
53 | path: '',
54 | redirect: '/home',
55 | },
56 | {
57 | path: '/home',
58 | name: 'Home',
59 | component: siteIndex,
60 | meta: {
61 | affix: true,
62 | title: '首页',
63 | },
64 | },
65 | {
66 | path: '/profile',
67 | name: 'Profile',
68 | component: siteProfile,
69 | meta: {
70 | title: '个人资料',
71 | },
72 | },
73 | ...adminRoute,
74 | ...adminLogRoute,
75 | ...adminGroupRoute,
76 | ...userRoute,
77 | ...userAddressRoute,
78 | ...shopBrandRoute,
79 | ...shopProductRoute,
80 | ...shopCategoryRoute,
81 | ...districtRoute,
82 | ...cmsCategoryRoute,
83 | ...cmsArticleRoute,
84 | {
85 | path: '*',
86 | component: notFound,
87 | meta: {
88 | title: '页面未找到',
89 | },
90 | },
91 | ],
92 | },
93 | ]
94 |
95 | const router = new VueRouter({
96 | base: process.env.VUE_APP_PUBLIC_PATH,
97 | mode: 'history',
98 | routes: routes,
99 | scrollBehavior: (to, from, savedPosition) => {
100 | if (savedPosition) {
101 | return savedPosition
102 | }
103 |
104 | if (to.hash) {
105 | return { selector: to.hash }
106 | }
107 |
108 | return { x: 0, y: 0 }
109 | },
110 | })
111 |
112 | Vue.use(VueRouterBackButton, { router, ignoreRoutesWithSameName: false })
113 |
114 | export default router
115 |
--------------------------------------------------------------------------------
/src/modules/admin/router/shop/brand.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/shop-brand',
4 | name: 'ShopBrand',
5 | component: () =>
6 | import(
7 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-brand/Index'
8 | ),
9 | meta: {
10 | title: '商品品牌',
11 | },
12 | },
13 | {
14 | path: '/shop-brand/create',
15 | name: 'ShopBrandCreate',
16 | component: () =>
17 | import(
18 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-brand/Create'
19 | ),
20 | meta: {
21 | title: '新建商品品牌',
22 | },
23 | },
24 | {
25 | path: '/shop-brand/edit/:id',
26 | name: 'ShopBrandEdit',
27 | component: () =>
28 | import(
29 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-brand/Edit'
30 | ),
31 | meta: {
32 | title: '编辑商品品牌',
33 | },
34 | },
35 | ]
36 |
--------------------------------------------------------------------------------
/src/modules/admin/router/shop/category.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/shop-category',
4 | component: () =>
5 | import(
6 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-category/Layout'
7 | ),
8 | children: [
9 | {
10 | path: '',
11 | name: 'ShopCategory',
12 | component: () =>
13 | import(
14 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-category/Index'
15 | ),
16 | meta: {
17 | title: '商品分类',
18 | },
19 | },
20 | {
21 | path: 'create/:id',
22 | name: 'ShopCategoryCreate',
23 | component: () =>
24 | import(
25 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-category/Create'
26 | ),
27 | meta: {
28 | title: '新建商品分类',
29 | parent: {
30 | name: 'ShopCategory',
31 | path: '/shop-category',
32 | title: '商品分类',
33 | },
34 | },
35 | },
36 | {
37 | path: 'edit/:id',
38 | name: 'ShopCategoryEdit',
39 | component: () =>
40 | import(
41 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-category/Edit'
42 | ),
43 | meta: {
44 | title: '编辑商品分类',
45 | parent: {
46 | name: 'ShopCategory',
47 | path: '/shop-category',
48 | title: '商品分类',
49 | },
50 | },
51 | },
52 | ],
53 | },
54 | ]
55 |
--------------------------------------------------------------------------------
/src/modules/admin/router/shop/product.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/shop-product',
4 | name: 'ShopProduct',
5 | component: () =>
6 | import(
7 | /* webpackChunkName: "chunk-shop" */ '@admin/views/shop-product/Index'
8 | ),
9 | meta: {
10 | title: '商品列表',
11 | },
12 | },
13 | ]
14 |
--------------------------------------------------------------------------------
/src/modules/admin/router/user/address.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/user-address',
4 | name: 'UserAddress',
5 | component: () =>
6 | import(
7 | /* webpackChunkName: "chunk-user" */ '@admin/views/user-address/Index'
8 | ),
9 | meta: {
10 | title: '用户地址',
11 | },
12 | },
13 | {
14 | path: '/user-address/create',
15 | name: 'UserAddressCreate',
16 | component: () =>
17 | import(
18 | /* webpackChunkName: "chunk-user" */ '@admin/views/user-address/Create'
19 | ),
20 | meta: {
21 | title: '新建用户地址',
22 | },
23 | },
24 | {
25 | path: '/user-address/edit/:id',
26 | name: 'UserAddressEdit',
27 | component: () =>
28 | import(
29 | /* webpackChunkName: "chunk-user" */ '@admin/views/user-address/Edit'
30 | ),
31 | meta: {
32 | title: '编辑用户地址',
33 | },
34 | },
35 | ]
36 |
--------------------------------------------------------------------------------
/src/modules/admin/router/user/user.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/user',
4 | name: 'User',
5 | component: () =>
6 | import(/* webpackChunkName: "chunk-user" */ '@admin/views/user/Index'),
7 | meta: {
8 | title: '用户',
9 | },
10 | },
11 | {
12 | path: '/user/create',
13 | name: 'UserCreate',
14 | component: () =>
15 | import(/* webpackChunkName: "chunk-user" */ '@admin/views/user/Create'),
16 | meta: {
17 | title: '新建用户',
18 | },
19 | },
20 | {
21 | path: '/user/edit/:id',
22 | name: 'UserEdit',
23 | component: () =>
24 | import(/* webpackChunkName: "chunk-user" */ '@admin/views/user/Edit'),
25 | meta: {
26 | title: '编辑用户',
27 | },
28 | },
29 | ]
30 |
--------------------------------------------------------------------------------
/src/modules/admin/services/AdminGroupService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const AdminGroupService = {
4 | all() {
5 | return Vue.http.get('admin-groups')
6 | },
7 |
8 | delete(id) {
9 | return Vue.http.delete('admin-groups/' + id)
10 | },
11 |
12 | create(adminGroup) {
13 | return Vue.http.post('admin-groups', adminGroup)
14 | },
15 |
16 | view(id) {
17 | return Vue.http.get('admin-groups/' + id)
18 | },
19 |
20 | edit(adminGroup) {
21 | return Vue.http.put('admin-groups/' + adminGroup.id, adminGroup)
22 | },
23 | }
24 |
25 | export default AdminGroupService
26 |
--------------------------------------------------------------------------------
/src/modules/admin/services/AdminService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const AdminService = {
4 | all() {
5 | return Vue.http.get('admins')
6 | },
7 |
8 | log(query) {
9 | return Vue.http.get('admin-logs', query)
10 | },
11 |
12 | delete(id) {
13 | return Vue.http.delete('admins/' + id)
14 | },
15 |
16 | create(admin) {
17 | return Vue.http.post('admins', admin)
18 | },
19 |
20 | view(id) {
21 | return Vue.http.get('admins/' + id)
22 | },
23 |
24 | edit(admin) {
25 | return Vue.http.put('admins/' + admin.id, admin)
26 | },
27 | }
28 |
29 | export default AdminService
30 |
--------------------------------------------------------------------------------
/src/modules/admin/services/AuthService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const AuthService = {
4 | login(credentials) {
5 | return Vue.http.post('auth/login', credentials, null, false)
6 | },
7 | logout() {
8 | return Vue.http.post('auth/logout', null, null, false)
9 | },
10 | account() {
11 | return Vue.http.get('auth/account', null, false)
12 | },
13 | profile(profile = null) {
14 | const endpoint = 'auth/profile'
15 |
16 | if (profile === null) {
17 | return Vue.http.get(endpoint)
18 | }
19 |
20 | return Vue.http.put(endpoint, profile)
21 | },
22 | }
23 |
24 | export default AuthService
25 |
--------------------------------------------------------------------------------
/src/modules/admin/services/CmsArticleService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const CmsArticleService = {
4 | all(query) {
5 | return Vue.http.get('cms-articles', query)
6 | },
7 |
8 | create(cmsArticle) {
9 | return Vue.http.post('cms-articles', cmsArticle)
10 | },
11 |
12 | view(id) {
13 | return Vue.http.get('cms-articles/' + id)
14 | },
15 |
16 | edit(cmsArticle) {
17 | return Vue.http.put('cms-articles/' + cmsArticle.id, cmsArticle)
18 | },
19 |
20 | delete(id) {
21 | return Vue.http.delete('cms-articles/' + id)
22 | },
23 | }
24 |
25 | export default CmsArticleService
26 |
--------------------------------------------------------------------------------
/src/modules/admin/services/CmsCategoryService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const CmsCategoryService = {
4 | create(cmsCategory) {
5 | return Vue.http.post('cms-categories', cmsCategory)
6 | },
7 |
8 | view(id) {
9 | return Vue.http.get('cms-categories/' + id, { withParents: true })
10 | },
11 |
12 | edit(cmsCategory) {
13 | return Vue.http.put('cms-categories/' + cmsCategory.id, cmsCategory)
14 | },
15 |
16 | delete(id) {
17 | return Vue.http.delete('cms-categories/' + id)
18 | },
19 | }
20 |
21 | export default CmsCategoryService
22 |
--------------------------------------------------------------------------------
/src/modules/admin/services/DistrictService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const DistrictService = {
4 | create(district) {
5 | return Vue.http.post('districts', district)
6 | },
7 |
8 | view(id) {
9 | return Vue.http.get('districts/' + id, { withParents: true })
10 | },
11 |
12 | edit(district) {
13 | return Vue.http.put('districts/' + district.id, district)
14 | },
15 |
16 | delete(id) {
17 | return Vue.http.delete('districts/' + id)
18 | },
19 | }
20 |
21 | export default DistrictService
22 |
--------------------------------------------------------------------------------
/src/modules/admin/services/MiscService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const MiscService = {
4 | districts(parentId) {
5 | return Vue.http.get('misc/districts', { parentId: parentId }, false)
6 | },
7 |
8 | districtPath(id) {
9 | return Vue.http.get('misc/district-path', { id: id }, false)
10 | },
11 |
12 | districtSearchTree(keyword) {
13 | return Vue.http.get(
14 | 'misc/district-search-tree',
15 | { keyword: keyword },
16 | false
17 | )
18 | },
19 |
20 | adminGroups() {
21 | return Vue.http.get('misc/admin-groups', null, false)
22 | },
23 |
24 | adminGroupPermissions() {
25 | return Vue.http.get('misc/admin-group-permissions', null, false)
26 | },
27 |
28 | shopCategoryTree() {
29 | return Vue.http.get('misc/shop-category-tree', null, false)
30 | },
31 |
32 | shopCategoryPath(id) {
33 | return Vue.http.get('misc/shop-category-path', { id: id }, false)
34 | },
35 |
36 | cmsCategoryTree() {
37 | return Vue.http.get('misc/cms-category-tree', null, false)
38 | },
39 |
40 | cmsCategoryPath(id) {
41 | return Vue.http.get('misc/cms-category-path', { id: id }, false)
42 | },
43 | }
44 |
45 | export default MiscService
46 |
--------------------------------------------------------------------------------
/src/modules/admin/services/OpenService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const OpenService = {
4 | captcha() {
5 | return Vue.http.get('open/captcha', null, false, true)
6 | },
7 | }
8 |
9 | export default OpenService
10 |
--------------------------------------------------------------------------------
/src/modules/admin/services/ShopBrandService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const ShopBrandService = {
4 | all(query) {
5 | return Vue.http.get('shop-brands', query)
6 | },
7 |
8 | create(shopBrand) {
9 | return Vue.http.post('shop-brands', shopBrand)
10 | },
11 |
12 | view(id) {
13 | return Vue.http.get('shop-brands/' + id)
14 | },
15 |
16 | edit(shopBrand) {
17 | return Vue.http.put('shop-brands/' + shopBrand.id, shopBrand)
18 | },
19 |
20 | delete(id) {
21 | return Vue.http.delete('shop-brands/' + id)
22 | },
23 | }
24 |
25 | export default ShopBrandService
26 |
--------------------------------------------------------------------------------
/src/modules/admin/services/ShopCategoryService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const ShopCategoryService = {
4 | create(shopCategory) {
5 | return Vue.http.post('shop-categories', shopCategory)
6 | },
7 |
8 | view(id) {
9 | return Vue.http.get('shop-categories/' + id, { withParents: true })
10 | },
11 |
12 | edit(shopCategory) {
13 | return Vue.http.put('shop-categories/' + shopCategory.id, shopCategory)
14 | },
15 |
16 | delete(id) {
17 | return Vue.http.delete('shop-categories/' + id)
18 | },
19 | }
20 |
21 | export default ShopCategoryService
22 |
--------------------------------------------------------------------------------
/src/modules/admin/services/ShopProductService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const ShopProductService = {
4 | all(query) {
5 | return Vue.http.get('shop-products', query)
6 | },
7 |
8 | create(shopProduct) {
9 | return Vue.http.post('shop-products', shopProduct)
10 | },
11 |
12 | view(id) {
13 | return Vue.http.get('shop-products/' + id)
14 | },
15 |
16 | edit(shopProduct) {
17 | return Vue.http.put('shop-products/' + shopProduct.id, shopProduct)
18 | },
19 |
20 | delete(id) {
21 | return Vue.http.delete('shop-products/' + id)
22 | },
23 | }
24 |
25 | export default ShopProductService
26 |
--------------------------------------------------------------------------------
/src/modules/admin/services/UserAddressService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const UserAddressService = {
4 | all(query) {
5 | return Vue.http.get('user-addresses', query)
6 | },
7 |
8 | delete(id) {
9 | return Vue.http.delete('user-addresses/' + id)
10 | },
11 |
12 | create(userAddress) {
13 | return Vue.http.post('user-addresses', userAddress)
14 | },
15 |
16 | view(id) {
17 | return Vue.http.get('user-addresses/' + id)
18 | },
19 |
20 | edit(userAddress) {
21 | return Vue.http.put('user-addresses/' + userAddress.id, userAddress)
22 | },
23 | }
24 |
25 | export default UserAddressService
26 |
--------------------------------------------------------------------------------
/src/modules/admin/services/UserService.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | const UserService = {
4 | all(query) {
5 | return Vue.http.get('users', query)
6 | },
7 |
8 | delete(id) {
9 | return Vue.http.delete('users/' + id)
10 | },
11 |
12 | create(user) {
13 | return Vue.http.post('users', user)
14 | },
15 |
16 | view(id) {
17 | return Vue.http.get('users/' + id)
18 | },
19 |
20 | edit(user) {
21 | return Vue.http.put('users/' + user.id, user)
22 | },
23 | }
24 |
25 | export default UserService
26 |
--------------------------------------------------------------------------------
/src/modules/admin/store/app.js:
--------------------------------------------------------------------------------
1 | import auth from './auth'
2 | import tabs from './tabs'
3 |
4 | const app = {
5 | strict: process.env.NODE_ENV !== 'production',
6 | modules: {
7 | auth: auth,
8 | tabs: tabs,
9 | },
10 | state: {
11 | error: {
12 | message: '',
13 | historyBack: false,
14 | },
15 | sidebar: {
16 | collapsed: window.matchMedia('(max-width: 991px)').matches,
17 | hidden: false,
18 | },
19 | },
20 | getters: {
21 | isSidebarCollapsed: (state) => {
22 | return state.sidebar.collapsed
23 | },
24 | },
25 | mutations: {
26 | TOGGLE_SIDEBAR: (state) => {
27 | state.sidebar.collapsed = !state.sidebar.collapsed
28 | },
29 | TOGGLE_ERROR: (state, error) => {
30 | state.error = error
31 | },
32 | },
33 | actions: {
34 | toggleSidebar({ commit }) {
35 | commit('TOGGLE_SIDEBAR')
36 | },
37 | setError({ commit }, error) {
38 | commit('TOGGLE_ERROR', error)
39 | },
40 | clearError({ commit }) {
41 | commit('TOGGLE_ERROR', { message: '', routeBack: false })
42 | },
43 | },
44 | }
45 |
46 | export default app
47 |
--------------------------------------------------------------------------------
/src/modules/admin/store/auth/index.js:
--------------------------------------------------------------------------------
1 | import { deepSearch, formatUrl } from '@core/utils/util'
2 |
3 | const clientIdKey = 'ag:admin-client-id'
4 | const accessTokenKey = 'ag:admin-access-token'
5 |
6 | const auth = {
7 | namespaced: true,
8 | state: {
9 | accessToken: window.localStorage.getItem(accessTokenKey) || '',
10 | loginAction: 'none', // none, modal, direct
11 | currentUser: null,
12 | groupMenus: [],
13 | groupMenusUrl: [],
14 | groupPermissions: [],
15 | },
16 | getters: {
17 | getAccessToken: (state) => {
18 | return {
19 | clientId: window.localStorage.getItem(clientIdKey),
20 | accessToken: state.accessToken,
21 | }
22 | },
23 | getCurrentUser: (state) => {
24 | return state.currentUser
25 | },
26 | getGroupMenus: (state) => {
27 | return state.groupMenus
28 | },
29 | isRouteInAcl: (state) => (route) => {
30 | if (route.length === 0) {
31 | return false
32 | }
33 |
34 | const routeSplit = route.split('/')
35 |
36 | return (
37 | state.groupPermissions.findIndex((permission) => {
38 | if (permission.length === 0) {
39 | return false
40 | }
41 |
42 | const permissionSplit = permission.split('/')
43 |
44 | let matched = true
45 |
46 | for (let i = permissionSplit.length - 1; i >= 0; i--) {
47 | if (permissionSplit[i].startsWith(':')) {
48 | continue
49 | }
50 |
51 | matched = permissionSplit[i] === routeSplit[i] && matched
52 | }
53 |
54 | return matched
55 | }) > -1
56 | )
57 | },
58 | isRouteInMenus: (state) => (route) => {
59 | const path = route.startsWith('/') ? route.substr(1) : route
60 | return state.groupMenusUrl.map((menu) => formatUrl(menu)).includes(path)
61 | },
62 | },
63 | mutations: {
64 | TOGGLE_ACCESS_TOKEN: (state, accessToken) => {
65 | state.accessToken = accessToken
66 | if (accessToken !== '') {
67 | window.localStorage.setItem(accessTokenKey, accessToken)
68 | } else {
69 | window.localStorage.removeItem(accessTokenKey)
70 | }
71 | },
72 | TOGGLE_LOGIN_ACTION: (state, action) => {
73 | state.loginAction = action
74 | },
75 | UPDATE_CURRENT_USER: (state, user) => {
76 | state.currentUser = user
77 | },
78 | UPDATE_GROUP_MENUS: (state, menus) => {
79 | state.groupMenus = menus
80 | state.groupMenusUrl = deepSearch('url', menus)
81 | },
82 | UPDATE_GROUP_PERMISSIONS: (state, permissions) => {
83 | state.groupPermissions = permissions
84 | },
85 | },
86 | actions: {
87 | init() {
88 | if (window.localStorage.getItem(clientIdKey) == null) {
89 | window.localStorage.setItem(
90 | clientIdKey,
91 | Math.random().toString(36).substr(2)
92 | )
93 | }
94 | },
95 | setLoginAction({ commit }, action) {
96 | commit('TOGGLE_LOGIN_ACTION', action)
97 | },
98 | login({ commit }, data) {
99 | commit('TOGGLE_LOGIN_ACTION', 'none')
100 | commit('TOGGLE_ACCESS_TOKEN', data.accessToken)
101 | commit('UPDATE_CURRENT_USER', data.currentUser)
102 | commit('UPDATE_GROUP_MENUS', data.groupMenus)
103 | commit('UPDATE_GROUP_PERMISSIONS', data.groupPermissions)
104 | },
105 | logout({ commit }) {
106 | commit('TOGGLE_ACCESS_TOKEN', '')
107 | commit('UPDATE_CURRENT_USER', null)
108 | commit('UPDATE_GROUP_MENUS', [])
109 | commit('UPDATE_GROUP_PERMISSIONS', [])
110 | },
111 | account({ commit }, data) {
112 | commit('UPDATE_CURRENT_USER', data.currentUser)
113 | commit('UPDATE_GROUP_MENUS', data.groupMenus)
114 | commit('UPDATE_GROUP_PERMISSIONS', data.groupPermissions)
115 | },
116 | },
117 | }
118 |
119 | export default auth
120 |
--------------------------------------------------------------------------------
/src/modules/admin/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import app from './app'
4 |
5 | Vue.use(Vuex)
6 |
7 | const store = new Vuex.Store(app)
8 |
9 | export default store
10 |
--------------------------------------------------------------------------------
/src/modules/admin/views/admin-group/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
59 |
--------------------------------------------------------------------------------
/src/modules/admin/views/admin-group/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
66 |
--------------------------------------------------------------------------------
/src/modules/admin/views/admin-group/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
19 |
20 |
27 | 编辑
28 |
29 |
36 | 删除
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
99 |
--------------------------------------------------------------------------------
/src/modules/admin/views/admin/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
62 |
--------------------------------------------------------------------------------
/src/modules/admin/views/admin/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
64 |
--------------------------------------------------------------------------------
/src/modules/admin/views/admin/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 |
57 | 编辑
58 |
59 |
66 | 删除
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
135 |
--------------------------------------------------------------------------------
/src/modules/admin/views/cms-article/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
63 |
--------------------------------------------------------------------------------
/src/modules/admin/views/cms-article/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
68 |
--------------------------------------------------------------------------------
/src/modules/admin/views/cms-category/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
16 |
17 |
18 |
19 |
20 |
21 |
99 |
--------------------------------------------------------------------------------
/src/modules/admin/views/cms-category/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
17 |
18 |
19 |
20 |
21 |
22 |
126 |
--------------------------------------------------------------------------------
/src/modules/admin/views/cms-category/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/src/modules/admin/views/cms-category/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
22 | 创建根分类
23 |
24 |
25 |
26 |
36 |
37 |
38 |
39 |
40 |
41 |
{{ data.name }}
42 |
43 |
44 |
51 |
58 |
59 |
60 |
61 |
62 |
63 |
68 |
69 |
70 |
71 |
72 |
73 |
154 |
--------------------------------------------------------------------------------
/src/modules/admin/views/district/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
15 |
16 |
17 |
18 |
19 |
20 |
97 |
--------------------------------------------------------------------------------
/src/modules/admin/views/district/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
16 |
17 |
18 |
19 |
20 |
21 |
108 |
--------------------------------------------------------------------------------
/src/modules/admin/views/district/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/src/modules/admin/views/district/_EditForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {{ submitText }}
57 |
58 |
59 |
67 | 删除
68 |
69 |
70 |
71 |
72 |
73 |
144 |
--------------------------------------------------------------------------------
/src/modules/admin/views/shop-brand/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
64 |
--------------------------------------------------------------------------------
/src/modules/admin/views/shop-brand/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
84 |
--------------------------------------------------------------------------------
/src/modules/admin/views/shop-category/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
16 |
17 |
18 |
19 |
20 |
21 |
98 |
--------------------------------------------------------------------------------
/src/modules/admin/views/shop-category/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
17 |
18 |
19 |
20 |
21 |
22 |
126 |
--------------------------------------------------------------------------------
/src/modules/admin/views/shop-category/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/src/modules/admin/views/shop-category/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
22 | 创建根分类
23 |
24 |
25 |
26 |
36 |
37 |
38 |
39 |
40 |
41 |
{{ data.name }}
42 |
43 |
44 |
51 |
58 |
59 |
60 |
61 |
62 |
63 |
68 |
69 |
70 |
71 |
72 |
73 |
154 |
--------------------------------------------------------------------------------
/src/modules/admin/views/site/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 单文件上传
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 单图片上传
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 缩略图上传
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 | 单图片上传切割
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 | 单图片上传切割加缩略图
50 |
51 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | 多文件上传
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 多图片上传
74 |
75 |
82 |
83 |
84 |
85 |
86 |
87 | 多图片上传切割
88 |
89 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
123 |
--------------------------------------------------------------------------------
/src/modules/admin/views/site/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
34 |
73 |
--------------------------------------------------------------------------------
/src/modules/admin/views/site/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
404
9 |
页面不存在
10 |
11 |
12 | 返回
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
31 |
66 |
--------------------------------------------------------------------------------
/src/modules/admin/views/site/Profile.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
61 |
--------------------------------------------------------------------------------
/src/modules/admin/views/user-address/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
56 |
--------------------------------------------------------------------------------
/src/modules/admin/views/user-address/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
66 |
--------------------------------------------------------------------------------
/src/modules/admin/views/user-address/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
17 |
18 |
19 |
25 |
26 |
27 |
28 |
29 | {{ scope.row.user.name }}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
43 | 编辑
44 |
45 |
52 | 删除
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
132 |
--------------------------------------------------------------------------------
/src/modules/admin/views/user-address/_EditForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
15 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {{ submitText }}
61 |
62 |
63 |
64 |
65 |
66 |
130 |
--------------------------------------------------------------------------------
/src/modules/admin/views/user/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
56 |
--------------------------------------------------------------------------------
/src/modules/admin/views/user/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
66 |
--------------------------------------------------------------------------------
/src/modules/mobile/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
29 |
30 |
39 |
--------------------------------------------------------------------------------
/src/modules/mobile/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huijiewei/agile-vue/d1bc73035a769c7364215a20351055913dd1f19e/src/modules/mobile/assets/images/logo.png
--------------------------------------------------------------------------------
/src/modules/mobile/components/DefaultLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
16 |
--------------------------------------------------------------------------------
/src/modules/mobile/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import router from './router'
4 | import store from './store'
5 |
6 | import App from './App.vue'
7 |
8 | Vue.config.productionTip = false
9 |
10 | new Vue({
11 | router,
12 | store,
13 | render: (h) => h(App),
14 | }).$mount('#root')
15 |
--------------------------------------------------------------------------------
/src/modules/mobile/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import VueRouterBackButton from 'vue-router-back-button'
4 |
5 | import siteIndex from '@mobile/views/site/Index'
6 | import notFound from '@mobile/views/site/NotFound'
7 |
8 | import defaultLayout from '@mobile/components/DefaultLayout'
9 |
10 | Vue.use(VueRouter)
11 |
12 | const routes = [
13 | {
14 | path: '/',
15 | component: defaultLayout,
16 | children: [
17 | {
18 | path: '',
19 | redirect: '/home',
20 | },
21 | {
22 | path: '/home',
23 | name: 'Home',
24 | component: siteIndex,
25 | meta: {
26 | affix: true,
27 | title: '首页',
28 | },
29 | },
30 | ],
31 | },
32 | {
33 | path: '*',
34 | component: notFound,
35 | },
36 | ]
37 |
38 | const router = new VueRouter({
39 | base: process.env.VUE_APP_PUBLIC_PATH,
40 | mode: 'history',
41 | routes: routes,
42 | scrollBehavior: (to, from, savedPosition) => {
43 | if (savedPosition) {
44 | return savedPosition
45 | }
46 |
47 | if (to.hash) {
48 | return { selector: to.hash }
49 | }
50 |
51 | return { x: 0, y: 0 }
52 | },
53 | })
54 |
55 | Vue.use(VueRouterBackButton, { router })
56 |
57 | export default router
58 |
--------------------------------------------------------------------------------
/src/modules/mobile/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | export default new Vuex.Store({
7 | state: {},
8 | mutations: {},
9 | actions: {},
10 | modules: {},
11 | })
12 |
--------------------------------------------------------------------------------
/src/modules/mobile/views/site/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
特点
5 |
6 |
7 |
8 |
21 |
--------------------------------------------------------------------------------
/src/modules/mobile/views/site/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
404
9 |
页面不存在
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
34 |
69 |
--------------------------------------------------------------------------------
/src/modules/website/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/modules/website/components/DefaultLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/src/modules/website/entry.client.js:
--------------------------------------------------------------------------------
1 | import { createApp } from './main'
2 |
3 | const { app } = createApp()
4 |
5 | app.$mount('#app')
6 |
--------------------------------------------------------------------------------
/src/modules/website/entry.server.js:
--------------------------------------------------------------------------------
1 | import { createApp } from './main'
2 |
3 | export default (context) => {
4 | return new Promise((resolve, reject) => {
5 | const { app, router } = createApp()
6 |
7 | router.push(context.url)
8 |
9 | router.onReady(() => {
10 | const matchedComponents = router.getMatchedComponents()
11 | if (!matchedComponents.length) {
12 | return reject({
13 | code: 404,
14 | })
15 | }
16 | resolve(app)
17 | }, reject)
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/src/modules/website/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import App from './App.vue'
4 |
5 | Vue.config.productionTip = false
6 |
7 | export function createApp() {
8 | const app = new Vue({
9 | render: (h) => h(App),
10 | })
11 | return { app }
12 | }
13 |
--------------------------------------------------------------------------------
/src/modules/website/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import DefaultLayout from '../components/DefaultLayout'
4 | import SiteIndex from '../views/site/Index'
5 |
6 | Vue.use(VueRouter)
7 |
8 | const routes = [
9 | {
10 | path: '/',
11 | component: DefaultLayout,
12 | children: [
13 | {
14 | path: '',
15 | redirect: '/home',
16 | },
17 | {
18 | path: '/home',
19 | name: 'Home',
20 | component: SiteIndex,
21 | meta: {
22 | affix: true,
23 | title: '首页',
24 | },
25 | },
26 | ],
27 | },
28 | ]
29 |
30 | const router = new VueRouter({
31 | base: '',
32 | mode: 'history',
33 | routes: routes,
34 | scrollBehavior: (to, from, savedPosition) => {
35 | if (savedPosition) {
36 | return savedPosition
37 | }
38 |
39 | if (to.hash) {
40 | return { selector: to.hash }
41 | }
42 |
43 | return { x: 0, y: 0 }
44 | },
45 | })
46 |
47 | Vue.use(router)
48 |
49 | export default router
50 |
--------------------------------------------------------------------------------
/src/modules/website/views/site/Index.vue:
--------------------------------------------------------------------------------
1 |
2 | Home
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const assign = require('assign-deep')
3 |
4 | const moduleConfig = require(process.env.MODULE_CONFIG)
5 |
6 | process.env.VUE_APP_QS_ARRAY_FORMAT = 'none'
7 | process.env.VUE_APP_PUBLIC_PATH = moduleConfig.appPath
8 |
9 | const vueConfig = {
10 | publicPath: moduleConfig.appPath,
11 | outputDir: moduleConfig.outputDir,
12 | assetsDir: 'assets',
13 | devServer: {
14 | host: 'www.agile.test',
15 | compress: true,
16 | },
17 | pwa: moduleConfig.pwaEnable
18 | ? {
19 | name: moduleConfig.appName,
20 | manifestOptions: {
21 | icons: [
22 | {
23 | src: 'icons/android-chrome-192x192.png',
24 | sizes: '192x192',
25 | type: 'image/png',
26 | },
27 | {
28 | src: 'icons/android-chrome-512x512.png',
29 | sizes: '512x512',
30 | type: 'image/png',
31 | },
32 | ],
33 | },
34 | iconPaths: {
35 | favicon16: 'icons/favicon-16x16.png',
36 | favicon32: 'icons/favicon-32x32.png',
37 | appleTouchIcon: 'icons/apple-touch-icon.png',
38 | maskIcon: 'icons/safari-pinned-tab.svg',
39 | msTileImage: 'icons/mstile-144x144.png',
40 | },
41 | }
42 | : null,
43 | chainWebpack: (config) => {
44 | config.entryPoints.delete('app')
45 |
46 | if (moduleConfig.appEntry) {
47 | for (const [key, value] of Object.entries(moduleConfig.appEntry)) {
48 | config.entry(key).add(value).end()
49 | }
50 | }
51 |
52 | config.resolve.alias.set('@core', path.resolve('src/core')).delete('@')
53 |
54 | if (moduleConfig.appAlias) {
55 | for (const [key, value] of Object.entries(moduleConfig.appAlias)) {
56 | config.resolve.alias.set(key, value)
57 | }
58 | }
59 |
60 | config.plugin('html').tap((args) => {
61 | args[0].template = 'public' + moduleConfig.appPath + '/index.html'
62 | args[0].title = moduleConfig.appName
63 | args[0].chunks = moduleConfig.appChunks
64 |
65 | return args
66 | })
67 |
68 | config.plugin('copy').tap((args) => {
69 | args[0][0].from = path.resolve('./public' + moduleConfig.appPath)
70 |
71 | args[0][1] = Object.assign({}, args[0][0], {
72 | from: path.resolve('./public/common'),
73 | })
74 |
75 | return args
76 | })
77 |
78 | if (moduleConfig.chainWebpack) {
79 | moduleConfig.chainWebpack(config)
80 | }
81 |
82 | config.optimization.splitChunks({
83 | chunks: 'all',
84 | cacheGroups: {
85 | vendor: {
86 | test: /[\\/]node_modules[\\/]/,
87 | chunks: 'initial',
88 | name: 'vendor',
89 | priority: 10,
90 | enforce: true,
91 | },
92 | element: {
93 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/,
94 | name: 'element',
95 | priority: 20,
96 | enforce: true,
97 | },
98 | agile: {
99 | test: /[\\/]src\/core[\\/]/,
100 | name: 'agile',
101 | priority: 5,
102 | enforce: true,
103 | },
104 | },
105 | })
106 | },
107 | }
108 |
109 | module.exports = assign(vueConfig, moduleConfig.vueConfig || {})
110 |
--------------------------------------------------------------------------------
/vue.entry.admin.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const apiMocker = require('mocker-api')
3 |
4 | module.exports = {
5 | appPath: '/admin',
6 | appName: 'Agile 管理后台',
7 | appEntry: {
8 | admin: './src/modules/admin/main.js',
9 | },
10 | appAlias: {
11 | '@admin': path.resolve('src/modules/admin'),
12 | },
13 | appChunks: ['vendor', 'element', 'agile', 'admin'],
14 | outputDir: 'dist/admin',
15 | pwaEnable: true,
16 | vueConfig: {
17 | devServer: {
18 | port: 8080,
19 | before(app) {
20 | apiMocker(app, path.resolve('./mocker/admin/index.js'))
21 | },
22 | },
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/vue.entry.mobile.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const apiMocker = require('mocker-api')
3 |
4 | module.exports = {
5 | appPath: '/mobile',
6 | appName: 'Agile 移动应用',
7 | appEntry: {
8 | mobile: './src/modules/mobile/main.js',
9 | },
10 | appAlias: {
11 | '@mobile': path.resolve('src/modules/mobile'),
12 | },
13 | appChunks: ['vendor', 'agile', 'mobile'],
14 | outputDir: 'dist/mobile',
15 | pwaEnable: true,
16 | vueConfig: {
17 | devServer: {
18 | port: 8081,
19 | before(app) {
20 | apiMocker(app, path.resolve('./mocker/mobile/index.js'))
21 | },
22 | },
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/vue.entry.website.client.js:
--------------------------------------------------------------------------------
1 | const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
2 |
3 | module.exports = {
4 | entry: './src/modules/website/entry.client.js',
5 | optimization: {
6 | runtimeChunk: {
7 | name: 'manifest',
8 | },
9 | },
10 | plugins: [new VueSSRClientPlugin()],
11 | }
12 |
--------------------------------------------------------------------------------
/vue.entry.website.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const assign = require('assign-deep')
3 | const vueConfig = require('./vue.config')
4 |
5 | const clientConfig = require('./vue.entry.website.client')
6 | const serverConfig = require('./vue.entry.website.server')
7 |
8 | const customConfig = {
9 | publicPath: '',
10 | outputDir: 'dist/website',
11 | configureWebpack:
12 | process.env.TARGET_ENV === 'server' ? serverConfig : clientConfig,
13 | devServer: {
14 | port: 8082,
15 | },
16 | }
17 |
18 | module.exports = assign(vueConfig, customConfig)
19 |
--------------------------------------------------------------------------------
/vue.entry.website.server.js:
--------------------------------------------------------------------------------
1 | const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
2 |
3 | module.exports = {
4 | entry: './src/modules/website/entry.server.js',
5 | target: 'node',
6 | output: {
7 | libraryTarget: 'commonjs2',
8 | },
9 | optimization: {
10 | splitChunks: false,
11 | },
12 | module: {
13 | rules: [],
14 | },
15 | plugins: [new VueSSRServerPlugin()],
16 | }
17 |
--------------------------------------------------------------------------------
/webstorm.config.js:
--------------------------------------------------------------------------------
1 | const webpackConfig = require('@vue/cli-service/webpack.config.js')
2 | module.exports = webpackConfig
3 |
--------------------------------------------------------------------------------