diff --git a/image/system.png b/image/system.png
new file mode 100644
index 0000000..361d211
Binary files /dev/null and b/image/system.png differ
diff --git a/sql/market-curd.sql b/sql/market-curd.sql
new file mode 100644
index 0000000..f9b17b0
--- /dev/null
+++ b/sql/market-curd.sql
@@ -0,0 +1,89 @@
+-- 员工表
+drop table if exists tb_employee;
+create table tb_employee(
+ emp_id int(11) comment '员工Id' primary key auto_increment,
+ emp_name varchar(55) comment '员工姓名',
+ emp_pw varchar(55) comment '员工密码',
+ emp_tel varchar(20) comment '员工手机号',
+ emp_id_card varchar(20) comment '员工身份证号',
+ emp_age int(11) comment '员工年龄',
+ emp_gender int(11) comment '员工性别:1-男 2-女',
+ emp_address varchar(20) comment '员工住址',
+ emp_position varchar(10) comment '员工职位',
+ emp_sal decimal(10,2) comment '员工薪资'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='员工表';
+insert into tb_employee (emp_name, emp_pw, emp_tel, emp_id_card, emp_age, emp_gender, emp_address, emp_position, emp_sal) values ('李四','lisi','13320560246','320145199702059783',26,1,'北京市大兴区XXX路XX号','普通员工',5000);
+select emp_id, emp_name, emp_pw, emp_tel, emp_id_card, emp_age, emp_gender, emp_address, emp_position, emp_sal from tb_employee;
+
+-- 领导表
+drop table if exists tb_manager;
+create table tb_manager(
+ manager_id int(11) comment '领导Id' primary key auto_increment,
+ manager_pw varchar(20) comment '领导密码',
+ emp_id int(11) comment '员工',
+ role_level varchar(10) comment '权限'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='领导表';
+insert into tb_manager (manager_id, manager_pw, emp_id, role_level) values ();
+select manager_id, manager_pw, emp_id, role_level from tb_manager;
+
+
+
+-- 会员表
+drop table if exists tb_vip;
+create table tb_vip(
+ vip_id int(11) comment '会员编号' primary key auto_increment,
+ vip_name varchar(20) comment '会员姓名',
+ vip_age int(2) comment '会员年龄',
+ vip_gender int(11) comment '员工性别:1-男 2-女',
+ vip_tel long comment '会员联系方式',
+ vip_grade varchar(1) comment '会员等级',
+ reg_date datetime comment '注册日期'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='会员表';
+insert into tb_vip (vip_name, vip_age, vip_gender, vip_tel, vip_grade, reg_date) values ('张三',20,1,'17373105689',3,'2020-1-1 10:00');
+select vip_id, vip_name, vip_age, vip_gender, vip_tel, vip_grade, reg_date from tb_vip;
+
+-- 客户表
+drop table if exists tb_customer;
+create table tb_customer(
+ customer_id int(11) comment '客户编号' primary key auto_increment,
+ customer_name varchar(20) comment '客户姓名',
+ customer_age varchar(20) comment '客户年龄',
+ customer_gender int(11) comment '客户性别:1-男 2-女',
+ customer_address varchar(20) comment '客户住址',
+ customer_tel varchar(20) comment '客户联系方式'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='客户表';
+insert into tb_customer (customer_name, customer_age, customer_gender, customer_address, customer_tel) values ('张三',20,1,'北京市大兴区XXX路XX号','17373105689');
+select customer_id, customer_name, customer_age, customer_gender, customer_address, customer_tel from tb_customer;
+
+
+-- 商品表
+drop table if exists tb_merch;
+create table tb_merch(
+ merch_id int(11) comment '商品编号' primary key auto_increment,
+ merch_name varchar(20) comment '商品名称',
+ merch_type varchar(10) comment '商品类型',
+ merch_price decimal(4,2) comment '价格',
+ bar_code varchar(20) comment '条形码',
+ sales_pro_price decimal(4,2) comment '促销价',
+ factory_id varchar(10) comment '厂商编号',
+ provide_id varchar(10) comment '供货商编号',
+ merch_dead_time datetime comment '过期日期',
+ merch_num int(4) comment '库存数量',
+ merch_sta int(10) default 2 comment '商品状态:1-未上架 2-上架 3-下架'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='商品表';
+insert into tb_merch (merch_name, merch_type, merch_price, bar_code, sales_pro_price, factory_id, provide_id, merch_dead_time, merch_num)
+values ('青芒','生鲜水果',30,'asfdghj',20,'001','0011','2023-11-30',500);
+select merch_id, merch_name, merch_type, merch_price, bar_code, sales_pro_price, factory_id, provide_id, merch_dead_time, merch_num, merch_sta from tb_merch;
+
+-- 进货表
+drop table if exists tb_import;
+create table tb_import(
+ list_id int(11) comment '表单编号' primary key auto_increment,
+ merch_id int(11) comment '商品编号',
+ merch_name varchar(20) comment '商品名称',
+ merch_type varchar(10) comment '商品类型',
+ merch_price decimal(4,2) comment '价格',
+ plan_num int(4) comment '计划进货数',
+ import_date date comment '进货日期',
+ provide_id varchar(10) comment '供货商编号'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='进货表';
diff --git a/vue-gg-market/.editorconfig b/vue-gg-market/.editorconfig
new file mode 100644
index 0000000..ea6e20f
--- /dev/null
+++ b/vue-gg-market/.editorconfig
@@ -0,0 +1,14 @@
+# http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/vue-gg-market/.env.development b/vue-gg-market/.env.development
new file mode 100644
index 0000000..de583d0
--- /dev/null
+++ b/vue-gg-market/.env.development
@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dev-api'
diff --git a/vue-gg-market/.env.production b/vue-gg-market/.env.production
new file mode 100644
index 0000000..80c8103
--- /dev/null
+++ b/vue-gg-market/.env.production
@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/prod-api'
+
diff --git a/vue-gg-market/.env.staging b/vue-gg-market/.env.staging
new file mode 100644
index 0000000..a8793a0
--- /dev/null
+++ b/vue-gg-market/.env.staging
@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/stage-api'
+
diff --git a/vue-gg-market/.eslintignore b/vue-gg-market/.eslintignore
new file mode 100644
index 0000000..e6529fc
--- /dev/null
+++ b/vue-gg-market/.eslintignore
@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist
diff --git a/vue-gg-market/.eslintrc.js b/vue-gg-market/.eslintrc.js
new file mode 100644
index 0000000..c977505
--- /dev/null
+++ b/vue-gg-market/.eslintrc.js
@@ -0,0 +1,198 @@
+module.exports = {
+ root: true,
+ parserOptions: {
+ parser: 'babel-eslint',
+ sourceType: 'module'
+ },
+ env: {
+ browser: true,
+ node: true,
+ es6: true,
+ },
+ extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+ // add your custom rules here
+ //it is base on https://github.com/vuejs/eslint-config-vue
+ rules: {
+ "vue/max-attributes-per-line": [2, {
+ "singleline": 10,
+ "multiline": {
+ "max": 1,
+ "allowFirstLine": false
+ }
+ }],
+ "vue/singleline-html-element-content-newline": "off",
+ "vue/multiline-html-element-content-newline":"off",
+ "vue/name-property-casing": ["error", "PascalCase"],
+ "vue/no-v-html": "off",
+ 'accessor-pairs': 2,
+ 'arrow-spacing': [2, {
+ 'before': true,
+ 'after': true
+ }],
+ 'block-spacing': [2, 'always'],
+ 'brace-style': [2, '1tbs', {
+ 'allowSingleLine': true
+ }],
+ 'camelcase': [0, {
+ 'properties': 'always'
+ }],
+ 'comma-dangle': [2, 'never'],
+ 'comma-spacing': [2, {
+ 'before': false,
+ 'after': true
+ }],
+ 'comma-style': [2, 'last'],
+ 'constructor-super': 2,
+ 'curly': [2, 'multi-line'],
+ 'dot-location': [2, 'property'],
+ 'eol-last': 2,
+ 'eqeqeq': ["error", "always", {"null": "ignore"}],
+ 'generator-star-spacing': [2, {
+ 'before': true,
+ 'after': true
+ }],
+ 'handle-callback-err': [2, '^(err|error)$'],
+ 'indent': [2, 2, {
+ 'SwitchCase': 1
+ }],
+ 'jsx-quotes': [2, 'prefer-single'],
+ 'key-spacing': [2, {
+ 'beforeColon': false,
+ 'afterColon': true
+ }],
+ 'keyword-spacing': [2, {
+ 'before': true,
+ 'after': true
+ }],
+ 'new-cap': [2, {
+ 'newIsCap': true,
+ 'capIsNew': false
+ }],
+ 'new-parens': 2,
+ 'no-array-constructor': 2,
+ 'no-caller': 2,
+ 'no-console': 'off',
+ 'no-class-assign': 2,
+ 'no-cond-assign': 2,
+ 'no-const-assign': 2,
+ 'no-control-regex': 0,
+ 'no-delete-var': 2,
+ 'no-dupe-args': 2,
+ 'no-dupe-class-members': 2,
+ 'no-dupe-keys': 2,
+ 'no-duplicate-case': 2,
+ 'no-empty-character-class': 2,
+ 'no-empty-pattern': 2,
+ 'no-eval': 2,
+ 'no-ex-assign': 2,
+ 'no-extend-native': 2,
+ 'no-extra-bind': 2,
+ 'no-extra-boolean-cast': 2,
+ 'no-extra-parens': [2, 'functions'],
+ 'no-fallthrough': 2,
+ 'no-floating-decimal': 2,
+ 'no-func-assign': 2,
+ 'no-implied-eval': 2,
+ 'no-inner-declarations': [2, 'functions'],
+ 'no-invalid-regexp': 2,
+ 'no-irregular-whitespace': 2,
+ 'no-iterator': 2,
+ 'no-label-var': 2,
+ 'no-labels': [2, {
+ 'allowLoop': false,
+ 'allowSwitch': false
+ }],
+ 'no-lone-blocks': 2,
+ 'no-mixed-spaces-and-tabs': 2,
+ 'no-multi-spaces': 2,
+ 'no-multi-str': 2,
+ 'no-multiple-empty-lines': [2, {
+ 'max': 1
+ }],
+ 'no-native-reassign': 2,
+ 'no-negated-in-lhs': 2,
+ 'no-new-object': 2,
+ 'no-new-require': 2,
+ 'no-new-symbol': 2,
+ 'no-new-wrappers': 2,
+ 'no-obj-calls': 2,
+ 'no-octal': 2,
+ 'no-octal-escape': 2,
+ 'no-path-concat': 2,
+ 'no-proto': 2,
+ 'no-redeclare': 2,
+ 'no-regex-spaces': 2,
+ 'no-return-assign': [2, 'except-parens'],
+ 'no-self-assign': 2,
+ 'no-self-compare': 2,
+ 'no-sequences': 2,
+ 'no-shadow-restricted-names': 2,
+ 'no-spaced-func': 2,
+ 'no-sparse-arrays': 2,
+ 'no-this-before-super': 2,
+ 'no-throw-literal': 2,
+ 'no-trailing-spaces': 2,
+ 'no-undef': 2,
+ 'no-undef-init': 2,
+ 'no-unexpected-multiline': 2,
+ 'no-unmodified-loop-condition': 2,
+ 'no-unneeded-ternary': [2, {
+ 'defaultAssignment': false
+ }],
+ 'no-unreachable': 2,
+ 'no-unsafe-finally': 2,
+ 'no-unused-vars': [2, {
+ 'vars': 'all',
+ 'args': 'none'
+ }],
+ 'no-useless-call': 2,
+ 'no-useless-computed-key': 2,
+ 'no-useless-constructor': 2,
+ 'no-useless-escape': 0,
+ 'no-whitespace-before-property': 2,
+ 'no-with': 2,
+ 'one-var': [2, {
+ 'initialized': 'never'
+ }],
+ 'operator-linebreak': [2, 'after', {
+ 'overrides': {
+ '?': 'before',
+ ':': 'before'
+ }
+ }],
+ 'padded-blocks': [2, 'never'],
+ 'quotes': [2, 'single', {
+ 'avoidEscape': true,
+ 'allowTemplateLiterals': true
+ }],
+ 'semi': [2, 'never'],
+ 'semi-spacing': [2, {
+ 'before': false,
+ 'after': true
+ }],
+ 'space-before-blocks': [2, 'always'],
+ 'space-before-function-paren': [2, 'never'],
+ 'space-in-parens': [2, 'never'],
+ 'space-infix-ops': 2,
+ 'space-unary-ops': [2, {
+ 'words': true,
+ 'nonwords': false
+ }],
+ 'spaced-comment': [2, 'always', {
+ 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+ }],
+ 'template-curly-spacing': [2, 'never'],
+ 'use-isnan': 2,
+ 'valid-typeof': 2,
+ 'wrap-iife': [2, 'any'],
+ 'yield-star-spacing': [2, 'both'],
+ 'yoda': [2, 'never'],
+ 'prefer-const': 2,
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+ 'object-curly-spacing': [2, 'always', {
+ objectsInObjects: false
+ }],
+ 'array-bracket-spacing': [2, 'never']
+ }
+}
diff --git a/vue-gg-market/.gitignore b/vue-gg-market/.gitignore
new file mode 100644
index 0000000..9ad28d2
--- /dev/null
+++ b/vue-gg-market/.gitignore
@@ -0,0 +1,16 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json
+tests/**/coverage/
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
diff --git a/vue-gg-market/.travis.yml b/vue-gg-market/.travis.yml
new file mode 100644
index 0000000..f4be7a0
--- /dev/null
+++ b/vue-gg-market/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js: 10
+script: npm run test
+notifications:
+ email: false
diff --git a/vue-gg-market/LICENSE b/vue-gg-market/LICENSE
new file mode 100644
index 0000000..6151575
--- /dev/null
+++ b/vue-gg-market/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-present PanJiaChen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vue-gg-market/README-zh.md b/vue-gg-market/README-zh.md
new file mode 100644
index 0000000..1beec9b
--- /dev/null
+++ b/vue-gg-market/README-zh.md
@@ -0,0 +1,111 @@
+# vue-admin-template
+
+> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
+
+[线上地址](http://panjiachen.github.io/vue-admin-template)
+
+[国内访问](https://panjiachen.gitee.io/vue-admin-template)
+
+目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
+
+
+ SPONSORED BY
+
+
+
+
+
+
+
+## Extra
+
+如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+## 相关项目
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
+
+- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
+- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
+- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
+- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
+- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
+
+## Build Setup
+
+```bash
+# 克隆项目
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# 进入项目目录
+cd vue-admin-template
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9528](http://localhost:9528)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+```
+
+更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## 购买贴纸
+
+你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
+
+## Demo
+
+
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [
](http://godban.github.io/browsers-support-badges/)IE / Edge | [
](http://godban.github.io/browsers-support-badges/)Firefox | [
](http://godban.github.io/browsers-support-badges/)Chrome | [
](http://godban.github.io/browsers-support-badges/)Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen
diff --git a/vue-gg-market/README.md b/vue-gg-market/README.md
new file mode 100644
index 0000000..fa54b78
--- /dev/null
+++ b/vue-gg-market/README.md
@@ -0,0 +1,99 @@
+# vue-admin-template
+
+English | [简体中文](./README-zh.md)
+
+> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
+
+**Live demo:** http://panjiachen.github.io/vue-admin-template
+
+
+**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
+
+
+ SPONSORED BY
+
+
+
+
+
+
+
+## Build Setup
+
+```bash
+# clone the project
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# enter the project directory
+cd vue-admin-template
+
+# install dependency
+npm install
+
+# develop
+npm run dev
+```
+
+This will automatically open http://localhost:9528
+
+## Build
+
+```bash
+# build for test environment
+npm run build:stage
+
+# build for production environment
+npm run build:prod
+```
+
+## Advanced
+
+```bash
+# preview the release environment effect
+npm run preview
+
+# preview the release environment effect + static resource analysis
+npm run preview -- --report
+
+# code format check
+npm run lint
+
+# code format check and auto fix
+npm run lint -- --fix
+```
+
+Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
+
+## Demo
+
+
+
+## Extra
+
+If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
+
+## Related Project
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [
](http://godban.github.io/browsers-support-badges/)IE / Edge | [
](http://godban.github.io/browsers-support-badges/)Firefox | [
](http://godban.github.io/browsers-support-badges/)Chrome | [
](http://godban.github.io/browsers-support-badges/)Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen
diff --git a/vue-gg-market/babel.config.js b/vue-gg-market/babel.config.js
new file mode 100644
index 0000000..fb82b27
--- /dev/null
+++ b/vue-gg-market/babel.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+ presets: [
+ // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
+ '@vue/cli-plugin-babel/preset'
+ ],
+ 'env': {
+ 'development': {
+ // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+ // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+ // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
+ 'plugins': ['dynamic-import-node']
+ }
+ }
+}
diff --git a/vue-gg-market/build/index.js b/vue-gg-market/build/index.js
new file mode 100644
index 0000000..0c57de2
--- /dev/null
+++ b/vue-gg-market/build/index.js
@@ -0,0 +1,35 @@
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+ const report = rawArgv.includes('--report')
+
+ run(`vue-cli-service build ${args}`)
+
+ const port = 9526
+ const publicPath = config.publicPath
+
+ var connect = require('connect')
+ var serveStatic = require('serve-static')
+ const app = connect()
+
+ app.use(
+ publicPath,
+ serveStatic('./dist', {
+ index: ['index.html', '/']
+ })
+ )
+
+ app.listen(port, function () {
+ console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
+ if (report) {
+ console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
+ }
+
+ })
+} else {
+ run(`vue-cli-service build ${args}`)
+}
diff --git a/vue-gg-market/jest.config.js b/vue-gg-market/jest.config.js
new file mode 100644
index 0000000..143cdc8
--- /dev/null
+++ b/vue-gg-market/jest.config.js
@@ -0,0 +1,24 @@
+module.exports = {
+ moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
+ transform: {
+ '^.+\\.vue$': 'vue-jest',
+ '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
+ 'jest-transform-stub',
+ '^.+\\.jsx?$': 'babel-jest'
+ },
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1'
+ },
+ snapshotSerializers: ['jest-serializer-vue'],
+ testMatch: [
+ '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
+ ],
+ collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
+ coverageDirectory: '/tests/unit/coverage',
+ // 'collectCoverage': true,
+ 'coverageReporters': [
+ 'lcov',
+ 'text-summary'
+ ],
+ testURL: 'http://localhost/'
+}
diff --git a/vue-gg-market/jsconfig.json b/vue-gg-market/jsconfig.json
new file mode 100644
index 0000000..ed079e2
--- /dev/null
+++ b/vue-gg-market/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/vue-gg-market/mock/index.js b/vue-gg-market/mock/index.js
new file mode 100644
index 0000000..c514c13
--- /dev/null
+++ b/vue-gg-market/mock/index.js
@@ -0,0 +1,57 @@
+const Mock = require('mockjs')
+const { param2Obj } = require('./utils')
+
+const user = require('./user')
+const table = require('./table')
+
+const mocks = [
+ ...user,
+ ...table
+]
+
+// for front mock
+// please use it cautiously, it will redefine XMLHttpRequest,
+// which will cause many of your third-party libraries to be invalidated(like progress event).
+function mockXHR() {
+ // mock patch
+ // https://github.com/nuysoft/Mock/issues/300
+ Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+ Mock.XHR.prototype.send = function() {
+ if (this.custom.xhr) {
+ this.custom.xhr.withCredentials = this.withCredentials || false
+
+ if (this.responseType) {
+ this.custom.xhr.responseType = this.responseType
+ }
+ }
+ this.proxy_send(...arguments)
+ }
+
+ function XHR2ExpressReqWrap(respond) {
+ return function(options) {
+ let result = null
+ if (respond instanceof Function) {
+ const { body, type, url } = options
+ // https://expressjs.com/en/4x/api.html#req
+ result = respond({
+ method: type,
+ body: JSON.parse(body),
+ query: param2Obj(url)
+ })
+ } else {
+ result = respond
+ }
+ return Mock.mock(result)
+ }
+ }
+
+ for (const i of mocks) {
+ Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
+ }
+}
+
+module.exports = {
+ mocks,
+ mockXHR
+}
+
diff --git a/vue-gg-market/mock/mock-server.js b/vue-gg-market/mock/mock-server.js
new file mode 100644
index 0000000..8941ec0
--- /dev/null
+++ b/vue-gg-market/mock/mock-server.js
@@ -0,0 +1,81 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+const Mock = require('mockjs')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+ let mockLastIndex
+ const { mocks } = require('./index.js')
+ const mocksForServer = mocks.map(route => {
+ return responseFake(route.url, route.type, route.response)
+ })
+ for (const mock of mocksForServer) {
+ app[mock.type](mock.url, mock.response)
+ mockLastIndex = app._router.stack.length
+ }
+ const mockRoutesLength = Object.keys(mocksForServer).length
+ return {
+ mockRoutesLength: mockRoutesLength,
+ mockStartIndex: mockLastIndex - mockRoutesLength
+ }
+}
+
+function unregisterRoutes() {
+ Object.keys(require.cache).forEach(i => {
+ if (i.includes(mockDir)) {
+ delete require.cache[require.resolve(i)]
+ }
+ })
+}
+
+// for mock server
+const responseFake = (url, type, respond) => {
+ return {
+ url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
+ type: type || 'get',
+ response(req, res) {
+ console.log('request invoke:' + req.path)
+ res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+ }
+ }
+}
+
+module.exports = app => {
+ // parse app.body
+ // https://expressjs.com/en/4x/api.html#req.body
+ app.use(bodyParser.json())
+ app.use(bodyParser.urlencoded({
+ extended: true
+ }))
+
+ const mockRoutes = registerRoutes(app)
+ var mockRoutesLength = mockRoutes.mockRoutesLength
+ var mockStartIndex = mockRoutes.mockStartIndex
+
+ // watch files, hot reload mock server
+ chokidar.watch(mockDir, {
+ ignored: /mock-server/,
+ ignoreInitial: true
+ }).on('all', (event, path) => {
+ if (event === 'change' || event === 'add') {
+ try {
+ // remove mock routes stack
+ app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+ // clear routes cache
+ unregisterRoutes()
+
+ const mockRoutes = registerRoutes(app)
+ mockRoutesLength = mockRoutes.mockRoutesLength
+ mockStartIndex = mockRoutes.mockStartIndex
+
+ console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
+ } catch (error) {
+ console.log(chalk.redBright(error))
+ }
+ }
+ })
+}
diff --git a/vue-gg-market/mock/table.js b/vue-gg-market/mock/table.js
new file mode 100644
index 0000000..bd0e013
--- /dev/null
+++ b/vue-gg-market/mock/table.js
@@ -0,0 +1,29 @@
+const Mock = require('mockjs')
+
+const data = Mock.mock({
+ 'items|30': [{
+ id: '@id',
+ title: '@sentence(10, 20)',
+ 'status|1': ['published', 'draft', 'deleted'],
+ author: 'name',
+ display_time: '@datetime',
+ pageviews: '@integer(300, 5000)'
+ }]
+})
+
+module.exports = [
+ {
+ url: '/vue-admin-template/table/list',
+ type: 'get',
+ response: config => {
+ const items = data.items
+ return {
+ code: 20000,
+ data: {
+ total: items.length,
+ items: items
+ }
+ }
+ }
+ }
+]
diff --git a/vue-gg-market/mock/user.js b/vue-gg-market/mock/user.js
new file mode 100644
index 0000000..7555338
--- /dev/null
+++ b/vue-gg-market/mock/user.js
@@ -0,0 +1,84 @@
+
+const tokens = {
+ admin: {
+ token: 'admin-token'
+ },
+ editor: {
+ token: 'editor-token'
+ }
+}
+
+const users = {
+ 'admin-token': {
+ roles: ['admin'],
+ introduction: 'I am a super administrator',
+ avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+ name: 'Super Admin'
+ },
+ 'editor-token': {
+ roles: ['editor'],
+ introduction: 'I am an editor',
+ avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+ name: 'Normal Editor'
+ }
+}
+
+module.exports = [
+ // user login
+ {
+ url: '/vue-admin-template/user/login',
+ type: 'post',
+ response: config => {
+ const { username } = config.body
+ const token = tokens[username]
+
+ // mock error
+ if (!token) {
+ return {
+ code: 60204,
+ message: 'Account and password are incorrect.'
+ }
+ }
+
+ return {
+ code: 20000,
+ data: token
+ }
+ }
+ },
+
+ // get user info
+ {
+ url: '/vue-admin-template/user/info\.*',
+ type: 'get',
+ response: config => {
+ const { token } = config.query
+ const info = users[token]
+
+ // mock error
+ if (!info) {
+ return {
+ code: 50008,
+ message: 'Login failed, unable to get user details.'
+ }
+ }
+
+ return {
+ code: 20000,
+ data: info
+ }
+ }
+ },
+
+ // user logout
+ {
+ url: '/vue-admin-template/user/logout',
+ type: 'post',
+ response: _ => {
+ return {
+ code: 20000,
+ data: 'success'
+ }
+ }
+ }
+]
diff --git a/vue-gg-market/mock/utils.js b/vue-gg-market/mock/utils.js
new file mode 100644
index 0000000..95cc27d
--- /dev/null
+++ b/vue-gg-market/mock/utils.js
@@ -0,0 +1,25 @@
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+function param2Obj(url) {
+ const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+ if (!search) {
+ return {}
+ }
+ const obj = {}
+ const searchArr = search.split('&')
+ searchArr.forEach(v => {
+ const index = v.indexOf('=')
+ if (index !== -1) {
+ const name = v.substring(0, index)
+ const val = v.substring(index + 1, v.length)
+ obj[name] = val
+ }
+ })
+ return obj
+}
+
+module.exports = {
+ param2Obj
+}
diff --git a/vue-gg-market/package.json b/vue-gg-market/package.json
new file mode 100644
index 0000000..2413824
--- /dev/null
+++ b/vue-gg-market/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "vue-admin-template",
+ "version": "4.4.0",
+ "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+ "author": "Pan ",
+ "scripts": {
+ "dev": "vue-cli-service serve",
+ "build:prod": "vue-cli-service build",
+ "build:stage": "vue-cli-service build --mode staging",
+ "preview": "node build/index.js --preview",
+ "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+ "lint": "eslint --ext .js,.vue src",
+ "test:unit": "jest --clearCache && vue-cli-service test:unit",
+ "test:ci": "npm run lint && npm run test:unit"
+ },
+ "dependencies": {
+ "axios": "0.18.1",
+ "core-js": "3.6.5",
+ "element-ui": "2.13.2",
+ "js-cookie": "2.2.0",
+ "normalize.css": "7.0.0",
+ "nprogress": "0.2.0",
+ "path-to-regexp": "2.4.0",
+ "vue": "2.6.10",
+ "vue-router": "3.0.6",
+ "vuex": "3.1.0"
+ },
+ "devDependencies": {
+ "@vue/cli-plugin-babel": "4.4.4",
+ "@vue/cli-plugin-eslint": "4.4.4",
+ "@vue/cli-plugin-unit-jest": "4.4.4",
+ "@vue/cli-service": "4.4.4",
+ "@vue/test-utils": "1.0.0-beta.29",
+ "autoprefixer": "9.5.1",
+ "babel-eslint": "10.1.0",
+ "babel-jest": "23.6.0",
+ "babel-plugin-dynamic-import-node": "2.3.3",
+ "chalk": "2.4.2",
+ "connect": "3.6.6",
+ "eslint": "6.7.2",
+ "eslint-plugin-vue": "6.2.2",
+ "html-webpack-plugin": "3.2.0",
+ "mockjs": "1.0.1-beta3",
+ "runjs": "4.3.2",
+ "sass": "1.26.8",
+ "sass-loader": "8.0.2",
+ "script-ext-html-webpack-plugin": "2.1.3",
+ "serve-static": "1.13.2",
+ "svg-sprite-loader": "4.1.3",
+ "svgo": "1.2.2",
+ "vue-template-compiler": "2.6.10"
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions"
+ ],
+ "engines": {
+ "node": ">=8.9",
+ "npm": ">= 3.0.0"
+ },
+ "license": "MIT"
+}
diff --git a/vue-gg-market/postcss.config.js b/vue-gg-market/postcss.config.js
new file mode 100644
index 0000000..10473ef
--- /dev/null
+++ b/vue-gg-market/postcss.config.js
@@ -0,0 +1,8 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+ 'plugins': {
+ // to edit target browsers: use "browserslist" field in package.json
+ 'autoprefixer': {}
+ }
+}
diff --git a/vue-gg-market/public/favicon.ico b/vue-gg-market/public/favicon.ico
new file mode 100644
index 0000000..34b63ac
Binary files /dev/null and b/vue-gg-market/public/favicon.ico differ
diff --git a/vue-gg-market/public/index.html b/vue-gg-market/public/index.html
new file mode 100644
index 0000000..fa2be91
--- /dev/null
+++ b/vue-gg-market/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ <%= webpackConfig.name %>
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/App.vue b/vue-gg-market/src/App.vue
new file mode 100644
index 0000000..ec9032c
--- /dev/null
+++ b/vue-gg-market/src/App.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/api/table.js b/vue-gg-market/src/api/table.js
new file mode 100644
index 0000000..2752f52
--- /dev/null
+++ b/vue-gg-market/src/api/table.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getList(params) {
+ return request({
+ url: '/vue-admin-template/table/list',
+ method: 'get',
+ params
+ })
+}
diff --git a/vue-gg-market/src/api/user.js b/vue-gg-market/src/api/user.js
new file mode 100644
index 0000000..c2a54b3
--- /dev/null
+++ b/vue-gg-market/src/api/user.js
@@ -0,0 +1,24 @@
+import request from '@/utils/request'
+
+export function login(data) {
+ return request({
+ url: '/auth/login',
+ method: 'post',
+ data
+ })
+}
+
+export function getInfo(token) {
+ return request({
+ url: '/auth/user/info',
+ method: 'get',
+ params: { token }
+ })
+}
+
+export function logout() {
+ return request({
+ url: '/auth/logout',
+ method: 'post'
+ })
+}
diff --git a/vue-gg-market/src/assets/404_images/404.png b/vue-gg-market/src/assets/404_images/404.png
new file mode 100644
index 0000000..3d8e230
Binary files /dev/null and b/vue-gg-market/src/assets/404_images/404.png differ
diff --git a/vue-gg-market/src/assets/404_images/404_cloud.png b/vue-gg-market/src/assets/404_images/404_cloud.png
new file mode 100644
index 0000000..c6281d0
Binary files /dev/null and b/vue-gg-market/src/assets/404_images/404_cloud.png differ
diff --git a/vue-gg-market/src/components/Breadcrumb/index.vue b/vue-gg-market/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..29f9a04
--- /dev/null
+++ b/vue-gg-market/src/components/Breadcrumb/index.vue
@@ -0,0 +1,78 @@
+
+
+
+
+ {{ item.meta.title }}
+ {{ item.meta.title }}
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/components/Hamburger/index.vue b/vue-gg-market/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..368b002
--- /dev/null
+++ b/vue-gg-market/src/components/Hamburger/index.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/components/SvgIcon/index.vue b/vue-gg-market/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..b07ded2
--- /dev/null
+++ b/vue-gg-market/src/components/SvgIcon/index.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/icons/index.js b/vue-gg-market/src/icons/index.js
new file mode 100644
index 0000000..2c6b309
--- /dev/null
+++ b/vue-gg-market/src/icons/index.js
@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg component
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const req = require.context('./svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+requireAll(req)
diff --git a/vue-gg-market/src/icons/svg/dashboard.svg b/vue-gg-market/src/icons/svg/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/example.svg b/vue-gg-market/src/icons/svg/example.svg
new file mode 100644
index 0000000..46f42b5
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/example.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/eye-open.svg b/vue-gg-market/src/icons/svg/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/eye-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/eye.svg b/vue-gg-market/src/icons/svg/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/form.svg b/vue-gg-market/src/icons/svg/form.svg
new file mode 100644
index 0000000..dcbaa18
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/form.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/link.svg b/vue-gg-market/src/icons/svg/link.svg
new file mode 100644
index 0000000..48197ba
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/nested.svg b/vue-gg-market/src/icons/svg/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/nested.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/password.svg b/vue-gg-market/src/icons/svg/password.svg
new file mode 100644
index 0000000..e291d85
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/password.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/table.svg b/vue-gg-market/src/icons/svg/table.svg
new file mode 100644
index 0000000..0e3dc9d
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/tree.svg b/vue-gg-market/src/icons/svg/tree.svg
new file mode 100644
index 0000000..dd4b7dd
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/tree.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svg/user.svg b/vue-gg-market/src/icons/svg/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/vue-gg-market/src/icons/svg/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vue-gg-market/src/icons/svgo.yml b/vue-gg-market/src/icons/svgo.yml
new file mode 100644
index 0000000..d11906a
--- /dev/null
+++ b/vue-gg-market/src/icons/svgo.yml
@@ -0,0 +1,22 @@
+# replace default config
+
+# multipass: true
+# full: true
+
+plugins:
+
+ # - name
+ #
+ # or:
+ # - name: false
+ # - name: true
+ #
+ # or:
+ # - name:
+ # param1: 1
+ # param2: 2
+
+- removeAttrs:
+ attrs:
+ - 'fill'
+ - 'fill-rule'
diff --git a/vue-gg-market/src/layout/components/AppMain.vue b/vue-gg-market/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..f6a3286
--- /dev/null
+++ b/vue-gg-market/src/layout/components/AppMain.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/components/Navbar.vue b/vue-gg-market/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..0ca5cf6
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Navbar.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/components/Sidebar/FixiOSBug.js b/vue-gg-market/src/layout/components/Sidebar/FixiOSBug.js
new file mode 100644
index 0000000..bc14856
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Sidebar/FixiOSBug.js
@@ -0,0 +1,26 @@
+export default {
+ computed: {
+ device() {
+ return this.$store.state.app.device
+ }
+ },
+ mounted() {
+ // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+ // https://github.com/PanJiaChen/vue-element-admin/issues/1135
+ this.fixBugIniOS()
+ },
+ methods: {
+ fixBugIniOS() {
+ const $subMenu = this.$refs.subMenu
+ if ($subMenu) {
+ const handleMouseleave = $subMenu.handleMouseleave
+ $subMenu.handleMouseleave = (e) => {
+ if (this.device === 'mobile') {
+ return
+ }
+ handleMouseleave(e)
+ }
+ }
+ }
+ }
+}
diff --git a/vue-gg-market/src/layout/components/Sidebar/Item.vue b/vue-gg-market/src/layout/components/Sidebar/Item.vue
new file mode 100644
index 0000000..aa1f5da
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Sidebar/Item.vue
@@ -0,0 +1,41 @@
+
+
+
diff --git a/vue-gg-market/src/layout/components/Sidebar/Link.vue b/vue-gg-market/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..530b3d5
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Sidebar/Link.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/components/Sidebar/Logo.vue b/vue-gg-market/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..040fab6
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/components/Sidebar/SidebarItem.vue b/vue-gg-market/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..a418c3d
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/components/Sidebar/index.vue b/vue-gg-market/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..da39034
--- /dev/null
+++ b/vue-gg-market/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/components/index.js b/vue-gg-market/src/layout/components/index.js
new file mode 100644
index 0000000..97ee3cd
--- /dev/null
+++ b/vue-gg-market/src/layout/components/index.js
@@ -0,0 +1,3 @@
+export { default as Navbar } from './Navbar'
+export { default as Sidebar } from './Sidebar'
+export { default as AppMain } from './AppMain'
diff --git a/vue-gg-market/src/layout/index.vue b/vue-gg-market/src/layout/index.vue
new file mode 100644
index 0000000..db22a7b
--- /dev/null
+++ b/vue-gg-market/src/layout/index.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/layout/mixin/ResizeHandler.js b/vue-gg-market/src/layout/mixin/ResizeHandler.js
new file mode 100644
index 0000000..e8d0df8
--- /dev/null
+++ b/vue-gg-market/src/layout/mixin/ResizeHandler.js
@@ -0,0 +1,45 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+ watch: {
+ $route(route) {
+ if (this.device === 'mobile' && this.sidebar.opened) {
+ store.dispatch('app/closeSideBar', { withoutAnimation: false })
+ }
+ }
+ },
+ beforeMount() {
+ window.addEventListener('resize', this.$_resizeHandler)
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', this.$_resizeHandler)
+ },
+ mounted() {
+ const isMobile = this.$_isMobile()
+ if (isMobile) {
+ store.dispatch('app/toggleDevice', 'mobile')
+ store.dispatch('app/closeSideBar', { withoutAnimation: true })
+ }
+ },
+ methods: {
+ // use $_ for mixins properties
+ // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+ $_isMobile() {
+ const rect = body.getBoundingClientRect()
+ return rect.width - 1 < WIDTH
+ },
+ $_resizeHandler() {
+ if (!document.hidden) {
+ const isMobile = this.$_isMobile()
+ store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+ if (isMobile) {
+ store.dispatch('app/closeSideBar', { withoutAnimation: true })
+ }
+ }
+ }
+ }
+}
diff --git a/vue-gg-market/src/main.js b/vue-gg-market/src/main.js
new file mode 100644
index 0000000..c5d6f13
--- /dev/null
+++ b/vue-gg-market/src/main.js
@@ -0,0 +1,43 @@
+import Vue from 'vue'
+
+import 'normalize.css/normalize.css' // A modern alternative to CSS resets
+
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
+
+import '@/styles/index.scss' // global css
+
+import App from './App'
+import store from './store'
+import router from './router'
+
+import '@/icons' // icon
+import '@/permission' // permission control
+
+/**
+ * If you don't want to use mock-server
+ * you want to use MockJs for mock api
+ * you can execute: mockXHR()
+ *
+ * Currently MockJs will be used in the production environment,
+ * please remove it before going online ! ! !
+ */
+if (process.env.NODE_ENV === 'production') {
+ const { mockXHR } = require('../mock')
+ mockXHR()
+}
+
+// set ElementUI lang to EN
+Vue.use(ElementUI, { locale })
+// 如果想要中文版 element-ui,按如下方式声明
+// Vue.use(ElementUI)
+
+Vue.config.productionTip = false
+
+new Vue({
+ el: '#app',
+ router,
+ store,
+ render: h => h(App)
+})
diff --git a/vue-gg-market/src/permission.js b/vue-gg-market/src/permission.js
new file mode 100644
index 0000000..fa1ea19
--- /dev/null
+++ b/vue-gg-market/src/permission.js
@@ -0,0 +1,64 @@
+import router from './router'
+import store from './store'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress' // progress bar
+import 'nprogress/nprogress.css' // progress bar style
+import { getToken } from '@/utils/auth' // get token from cookie
+import getPageTitle from '@/utils/get-page-title'
+
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
+
+const whiteList = ['/login'] // no redirect whitelist
+
+router.beforeEach(async(to, from, next) => {
+ // start progress bar
+ NProgress.start()
+
+ // set page title
+ document.title = getPageTitle(to.meta.title)
+
+ // determine whether the user has logged in
+ const hasToken = getToken()
+
+ if (hasToken) {
+ if (to.path === '/login') {
+ // if is logged in, redirect to the home page
+ next({ path: '/' })
+ NProgress.done()
+ } else {
+ const hasGetUserInfo = store.getters.name
+ if (hasGetUserInfo) {
+ next()
+ } else {
+ try {
+ // get user info
+ await store.dispatch('user/getInfo')
+
+ next()
+ } catch (error) {
+ // remove token and go to login page to re-login
+ await store.dispatch('user/resetToken')
+ Message.error(error || 'Has Error')
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
+ }
+ }
+ }
+ } else {
+ /* has no token*/
+
+ if (whiteList.indexOf(to.path) !== -1) {
+ // in the free login whitelist, go directly
+ next()
+ } else {
+ // other pages that do not have permission to access are redirected to the login page.
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
+ }
+ }
+})
+
+router.afterEach(() => {
+ // finish progress bar
+ NProgress.done()
+})
diff --git a/vue-gg-market/src/router/index.js b/vue-gg-market/src/router/index.js
new file mode 100644
index 0000000..13459e9
--- /dev/null
+++ b/vue-gg-market/src/router/index.js
@@ -0,0 +1,181 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '@/layout'
+
+/**
+ * Note: sub-menu only appear when route children.length >= 1
+ * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
+ *
+ * hidden: true if set true, item will not show in the sidebar(default is false)
+ * alwaysShow: true if set true, will always show the root menu
+ * if not set alwaysShow, when item has more than one children route,
+ * it will becomes nested mode, otherwise not show the root menu
+ * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
+ * name:'router-name' the name is used by (must set!!!)
+ * meta : {
+ roles: ['admin','editor'] control the page roles (you can set multiple roles)
+ title: 'title' the name show in sidebar and breadcrumb (recommend set)
+ icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
+ breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
+ activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
+ }
+ */
+
+/**
+ * constantRoutes
+ * a base page that does not have permission requirements
+ * all roles can be accessed
+ */
+export const constantRoutes = [
+ {
+ path: '/login',
+ component: () => import('@/views/login/index'),
+ hidden: true
+ },
+
+ {
+ path: '/404',
+ component: () => import('@/views/404'),
+ hidden: true
+ },
+
+ {
+ path: '/',
+ component: Layout,
+ redirect: '/dashboard',
+ children: [{
+ path: 'dashboard',
+ name: 'Dashboard',
+ component: () => import('@/views/dashboard/index'),
+ meta: { title: 'Dashboard', icon: 'dashboard' }
+ }]
+ },
+
+ {
+ path: '/example',
+ component: Layout,
+ redirect: '/example/table',
+ name: 'Example',
+ meta: { title: 'Example', icon: 'el-icon-s-help' },
+ children: [
+ {
+ path: 'table',
+ name: 'Table',
+ component: () => import('@/views/table/index'),
+ meta: { title: 'Table', icon: 'table' }
+ },
+ {
+ path: 'tree',
+ name: 'Tree',
+ component: () => import('@/views/tree/index'),
+ meta: { title: 'Tree', icon: 'tree' }
+ }
+ ]
+ },
+
+ {
+ path: '/form',
+ component: Layout,
+ children: [
+ {
+ path: 'index',
+ name: 'Form',
+ component: () => import('@/views/form/index'),
+ meta: { title: 'Form', icon: 'form' }
+ }
+ ]
+ },
+
+ {
+ path: '/nested',
+ component: Layout,
+ redirect: '/nested/menu1',
+ name: 'Nested',
+ meta: {
+ title: 'Nested',
+ icon: 'nested'
+ },
+ children: [
+ {
+ path: 'menu1',
+ component: () => import('@/views/nested/menu1/index'), // Parent router-view
+ name: 'Menu1',
+ meta: { title: 'Menu1' },
+ children: [
+ {
+ path: 'menu1-1',
+ component: () => import('@/views/nested/menu1/menu1-1'),
+ name: 'Menu1-1',
+ meta: { title: 'Menu1-1' }
+ },
+ {
+ path: 'menu1-2',
+ component: () => import('@/views/nested/menu1/menu1-2'),
+ name: 'Menu1-2',
+ meta: { title: 'Menu1-2' },
+ children: [
+ {
+ path: 'menu1-2-1',
+ component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
+ name: 'Menu1-2-1',
+ meta: { title: 'Menu1-2-1' }
+ },
+ {
+ path: 'menu1-2-2',
+ component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
+ name: 'Menu1-2-2',
+ meta: { title: 'Menu1-2-2' }
+ }
+ ]
+ },
+ {
+ path: 'menu1-3',
+ component: () => import('@/views/nested/menu1/menu1-3'),
+ name: 'Menu1-3',
+ meta: { title: 'Menu1-3' }
+ }
+ ]
+ },
+ {
+ path: 'menu2',
+ component: () => import('@/views/nested/menu2/index'),
+ name: 'Menu2',
+ meta: { title: 'menu2' }
+ }
+ ]
+ },
+
+ {
+ path: 'external-link',
+ component: Layout,
+ children: [
+ {
+ path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
+ meta: { title: 'External Link', icon: 'link' }
+ }
+ ]
+ },
+
+ // 404 page must be placed at the end !!!
+ { path: '*', redirect: '/404', hidden: true }
+]
+
+const createRouter = () => new Router({
+ // mode: 'history', // require service support
+ scrollBehavior: () => ({ y: 0 }),
+ routes: constantRoutes
+})
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+ const newRouter = createRouter()
+ router.matcher = newRouter.matcher // reset router
+}
+
+export default router
diff --git a/vue-gg-market/src/settings.js b/vue-gg-market/src/settings.js
new file mode 100644
index 0000000..ae3c494
--- /dev/null
+++ b/vue-gg-market/src/settings.js
@@ -0,0 +1,16 @@
+module.exports = {
+
+ title: 'Vue Admin Template',
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether fix the header
+ */
+ fixedHeader: false,
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether show the logo in sidebar
+ */
+ sidebarLogo: false
+}
diff --git a/vue-gg-market/src/store/getters.js b/vue-gg-market/src/store/getters.js
new file mode 100644
index 0000000..5ab7b4c
--- /dev/null
+++ b/vue-gg-market/src/store/getters.js
@@ -0,0 +1,8 @@
+const getters = {
+ sidebar: state => state.app.sidebar,
+ device: state => state.app.device,
+ token: state => state.user.token,
+ avatar: state => state.user.avatar,
+ name: state => state.user.name
+}
+export default getters
diff --git a/vue-gg-market/src/store/index.js b/vue-gg-market/src/store/index.js
new file mode 100644
index 0000000..6be466a
--- /dev/null
+++ b/vue-gg-market/src/store/index.js
@@ -0,0 +1,19 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import app from './modules/app'
+import settings from './modules/settings'
+import user from './modules/user'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+ modules: {
+ app,
+ settings,
+ user
+ },
+ getters
+})
+
+export default store
diff --git a/vue-gg-market/src/store/modules/app.js b/vue-gg-market/src/store/modules/app.js
new file mode 100644
index 0000000..7ea7e33
--- /dev/null
+++ b/vue-gg-market/src/store/modules/app.js
@@ -0,0 +1,48 @@
+import Cookies from 'js-cookie'
+
+const state = {
+ sidebar: {
+ opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+ withoutAnimation: false
+ },
+ device: 'desktop'
+}
+
+const mutations = {
+ TOGGLE_SIDEBAR: state => {
+ state.sidebar.opened = !state.sidebar.opened
+ state.sidebar.withoutAnimation = false
+ if (state.sidebar.opened) {
+ Cookies.set('sidebarStatus', 1)
+ } else {
+ Cookies.set('sidebarStatus', 0)
+ }
+ },
+ CLOSE_SIDEBAR: (state, withoutAnimation) => {
+ Cookies.set('sidebarStatus', 0)
+ state.sidebar.opened = false
+ state.sidebar.withoutAnimation = withoutAnimation
+ },
+ TOGGLE_DEVICE: (state, device) => {
+ state.device = device
+ }
+}
+
+const actions = {
+ toggleSideBar({ commit }) {
+ commit('TOGGLE_SIDEBAR')
+ },
+ closeSideBar({ commit }, { withoutAnimation }) {
+ commit('CLOSE_SIDEBAR', withoutAnimation)
+ },
+ toggleDevice({ commit }, device) {
+ commit('TOGGLE_DEVICE', device)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/vue-gg-market/src/store/modules/settings.js b/vue-gg-market/src/store/modules/settings.js
new file mode 100644
index 0000000..b3f33f8
--- /dev/null
+++ b/vue-gg-market/src/store/modules/settings.js
@@ -0,0 +1,32 @@
+import defaultSettings from '@/settings'
+
+const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
+
+const state = {
+ showSettings: showSettings,
+ fixedHeader: fixedHeader,
+ sidebarLogo: sidebarLogo
+}
+
+const mutations = {
+ CHANGE_SETTING: (state, { key, value }) => {
+ // eslint-disable-next-line no-prototype-builtins
+ if (state.hasOwnProperty(key)) {
+ state[key] = value
+ }
+ }
+}
+
+const actions = {
+ changeSetting({ commit }, data) {
+ commit('CHANGE_SETTING', data)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
+
diff --git a/vue-gg-market/src/store/modules/user.js b/vue-gg-market/src/store/modules/user.js
new file mode 100644
index 0000000..2f6423f
--- /dev/null
+++ b/vue-gg-market/src/store/modules/user.js
@@ -0,0 +1,97 @@
+import { login, logout, getInfo } from '@/api/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { resetRouter } from '@/router'
+
+const getDefaultState = () => {
+ return {
+ token: getToken(),
+ name: '',
+ avatar: ''
+ }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+ RESET_STATE: (state) => {
+ Object.assign(state, getDefaultState())
+ },
+ SET_TOKEN: (state, token) => {
+ state.token = token
+ },
+ SET_NAME: (state, name) => {
+ state.name = name
+ },
+ SET_AVATAR: (state, avatar) => {
+ state.avatar = avatar
+ }
+}
+
+const actions = {
+ // user login
+ login({ commit }, userInfo) {
+ const { username, password } = userInfo
+ return new Promise((resolve, reject) => {
+ login({ username: username.trim(), password: password }).then(response => {
+ const { data } = response
+ commit('SET_TOKEN', data.token)
+ setToken(data.token)
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // get user info
+ getInfo({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ getInfo(state.token).then(response => {
+ const { data } = response
+
+ if (!data) {
+ return reject('Verification failed, please Login again.')
+ }
+
+ const { name, avatar } = data
+
+ commit('SET_NAME', name)
+ commit('SET_AVATAR', avatar)
+ resolve(data)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // user logout
+ logout({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ logout(state.token).then(() => {
+ removeToken() // must remove token first
+ resetRouter()
+ commit('RESET_STATE')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // remove token
+ resetToken({ commit }) {
+ return new Promise(resolve => {
+ removeToken() // must remove token first
+ commit('RESET_STATE')
+ resolve()
+ })
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
+
diff --git a/vue-gg-market/src/styles/element-ui.scss b/vue-gg-market/src/styles/element-ui.scss
new file mode 100644
index 0000000..0062411
--- /dev/null
+++ b/vue-gg-market/src/styles/element-ui.scss
@@ -0,0 +1,49 @@
+// cover some element-ui styles
+
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+ font-weight: 400 !important;
+}
+
+.el-upload {
+ input[type="file"] {
+ display: none !important;
+ }
+}
+
+.el-upload__input {
+ display: none;
+}
+
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+ transform: none;
+ left: 0;
+ position: relative;
+ margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+ .el-upload {
+ width: 100%;
+
+ .el-upload-dragger {
+ width: 100%;
+ height: 200px;
+ }
+ }
+}
+
+// dropdown
+.el-dropdown-menu {
+ a {
+ display: block
+ }
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+ box-sizing: content-box;
+}
diff --git a/vue-gg-market/src/styles/index.scss b/vue-gg-market/src/styles/index.scss
new file mode 100644
index 0000000..3b4da51
--- /dev/null
+++ b/vue-gg-market/src/styles/index.scss
@@ -0,0 +1,65 @@
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+
+body {
+ height: 100%;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+ font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+ font-weight: 700;
+}
+
+html {
+ height: 100%;
+ box-sizing: border-box;
+}
+
+#app {
+ height: 100%;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+a:focus,
+a:active {
+ outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+ cursor: pointer;
+ color: inherit;
+ text-decoration: none;
+}
+
+div:focus {
+ outline: none;
+}
+
+.clearfix {
+ &:after {
+ visibility: hidden;
+ display: block;
+ font-size: 0;
+ content: " ";
+ clear: both;
+ height: 0;
+ }
+}
+
+// main-container global css
+.app-container {
+ padding: 20px;
+}
diff --git a/vue-gg-market/src/styles/mixin.scss b/vue-gg-market/src/styles/mixin.scss
new file mode 100644
index 0000000..36b74bb
--- /dev/null
+++ b/vue-gg-market/src/styles/mixin.scss
@@ -0,0 +1,28 @@
+@mixin clearfix {
+ &:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+}
+
+@mixin scrollBar {
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+}
+
+@mixin relative {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
diff --git a/vue-gg-market/src/styles/sidebar.scss b/vue-gg-market/src/styles/sidebar.scss
new file mode 100644
index 0000000..94760cc
--- /dev/null
+++ b/vue-gg-market/src/styles/sidebar.scss
@@ -0,0 +1,226 @@
+#app {
+
+ .main-container {
+ min-height: 100%;
+ transition: margin-left .28s;
+ margin-left: $sideBarWidth;
+ position: relative;
+ }
+
+ .sidebar-container {
+ transition: width 0.28s;
+ width: $sideBarWidth !important;
+ background-color: $menuBg;
+ height: 100%;
+ position: fixed;
+ font-size: 0px;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1001;
+ overflow: hidden;
+
+ // reset element-ui css
+ .horizontal-collapse-transition {
+ transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+ }
+
+ .scrollbar-wrapper {
+ overflow-x: hidden !important;
+ }
+
+ .el-scrollbar__bar.is-vertical {
+ right: 0px;
+ }
+
+ .el-scrollbar {
+ height: 100%;
+ }
+
+ &.has-logo {
+ .el-scrollbar {
+ height: calc(100% - 50px);
+ }
+ }
+
+ .is-horizontal {
+ display: none;
+ }
+
+ a {
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ }
+
+ .svg-icon {
+ margin-right: 16px;
+ }
+
+ .sub-el-icon {
+ margin-right: 12px;
+ margin-left: -2px;
+ }
+
+ .el-menu {
+ border: none;
+ height: 100%;
+ width: 100% !important;
+ }
+
+ // menu hover
+ .submenu-title-noDropdown,
+ .el-submenu__title {
+ &:hover {
+ background-color: $menuHover !important;
+ }
+ }
+
+ .is-active>.el-submenu__title {
+ color: $subMenuActiveText !important;
+ }
+
+ & .nest-menu .el-submenu>.el-submenu__title,
+ & .el-submenu .el-menu-item {
+ min-width: $sideBarWidth !important;
+ background-color: $subMenuBg !important;
+
+ &:hover {
+ background-color: $subMenuHover !important;
+ }
+ }
+ }
+
+ .hideSidebar {
+ .sidebar-container {
+ width: 54px !important;
+ }
+
+ .main-container {
+ margin-left: 54px;
+ }
+
+ .submenu-title-noDropdown {
+ padding: 0 !important;
+ position: relative;
+
+ .el-tooltip {
+ padding: 0 !important;
+
+ .svg-icon {
+ margin-left: 20px;
+ }
+
+ .sub-el-icon {
+ margin-left: 19px;
+ }
+ }
+ }
+
+ .el-submenu {
+ overflow: hidden;
+
+ &>.el-submenu__title {
+ padding: 0 !important;
+
+ .svg-icon {
+ margin-left: 20px;
+ }
+
+ .sub-el-icon {
+ margin-left: 19px;
+ }
+
+ .el-submenu__icon-arrow {
+ display: none;
+ }
+ }
+ }
+
+ .el-menu--collapse {
+ .el-submenu {
+ &>.el-submenu__title {
+ &>span {
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ visibility: hidden;
+ display: inline-block;
+ }
+ }
+ }
+ }
+ }
+
+ .el-menu--collapse .el-menu .el-submenu {
+ min-width: $sideBarWidth !important;
+ }
+
+ // mobile responsive
+ .mobile {
+ .main-container {
+ margin-left: 0px;
+ }
+
+ .sidebar-container {
+ transition: transform .28s;
+ width: $sideBarWidth !important;
+ }
+
+ &.hideSidebar {
+ .sidebar-container {
+ pointer-events: none;
+ transition-duration: 0.3s;
+ transform: translate3d(-$sideBarWidth, 0, 0);
+ }
+ }
+ }
+
+ .withoutAnimation {
+
+ .main-container,
+ .sidebar-container {
+ transition: none;
+ }
+ }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+ &>.el-menu {
+ .svg-icon {
+ margin-right: 16px;
+ }
+ .sub-el-icon {
+ margin-right: 12px;
+ margin-left: -2px;
+ }
+ }
+
+ .nest-menu .el-submenu>.el-submenu__title,
+ .el-menu-item {
+ &:hover {
+ // you can use $subMenuHover
+ background-color: $menuHover !important;
+ }
+ }
+
+ // the scroll bar appears when the subMenu is too long
+ >.el-menu--popup {
+ max-height: 100vh;
+ overflow-y: auto;
+
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+ }
+}
diff --git a/vue-gg-market/src/styles/transition.scss b/vue-gg-market/src/styles/transition.scss
new file mode 100644
index 0000000..4cb27cc
--- /dev/null
+++ b/vue-gg-market/src/styles/transition.scss
@@ -0,0 +1,48 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+ opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+ transition: all .5s;
+}
+
+.fade-transform-enter {
+ opacity: 0;
+ transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+ opacity: 0;
+ transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+ transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.breadcrumb-move {
+ transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+ position: absolute;
+}
diff --git a/vue-gg-market/src/styles/variables.scss b/vue-gg-market/src/styles/variables.scss
new file mode 100644
index 0000000..be55772
--- /dev/null
+++ b/vue-gg-market/src/styles/variables.scss
@@ -0,0 +1,25 @@
+// sidebar
+$menuText:#bfcbd9;
+$menuActiveText:#409EFF;
+$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
+
+$menuBg:#304156;
+$menuHover:#263445;
+
+$subMenuBg:#1f2d3d;
+$subMenuHover:#001528;
+
+$sideBarWidth: 210px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+ menuText: $menuText;
+ menuActiveText: $menuActiveText;
+ subMenuActiveText: $subMenuActiveText;
+ menuBg: $menuBg;
+ menuHover: $menuHover;
+ subMenuBg: $subMenuBg;
+ subMenuHover: $subMenuHover;
+ sideBarWidth: $sideBarWidth;
+}
diff --git a/vue-gg-market/src/utils/auth.js b/vue-gg-market/src/utils/auth.js
new file mode 100644
index 0000000..059af18
--- /dev/null
+++ b/vue-gg-market/src/utils/auth.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'vue_admin_template_token'
+
+export function getToken() {
+ return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+ return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+ return Cookies.remove(TokenKey)
+}
diff --git a/vue-gg-market/src/utils/get-page-title.js b/vue-gg-market/src/utils/get-page-title.js
new file mode 100644
index 0000000..a6de99d
--- /dev/null
+++ b/vue-gg-market/src/utils/get-page-title.js
@@ -0,0 +1,10 @@
+import defaultSettings from '@/settings'
+
+const title = defaultSettings.title || 'Vue Admin Template'
+
+export default function getPageTitle(pageTitle) {
+ if (pageTitle) {
+ return `${pageTitle} - ${title}`
+ }
+ return `${title}`
+}
diff --git a/vue-gg-market/src/utils/index.js b/vue-gg-market/src/utils/index.js
new file mode 100644
index 0000000..4830c04
--- /dev/null
+++ b/vue-gg-market/src/utils/index.js
@@ -0,0 +1,117 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string | null}
+ */
+export function parseTime(time, cFormat) {
+ if (arguments.length === 0 || !time) {
+ return null
+ }
+ const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+ let date
+ if (typeof time === 'object') {
+ date = time
+ } else {
+ if ((typeof time === 'string')) {
+ if ((/^[0-9]+$/.test(time))) {
+ // support "1548221490638"
+ time = parseInt(time)
+ } else {
+ // support safari
+ // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
+ time = time.replace(new RegExp(/-/gm), '/')
+ }
+ }
+
+ if ((typeof time === 'number') && (time.toString().length === 10)) {
+ time = time * 1000
+ }
+ date = new Date(time)
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ }
+ const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+ const value = formatObj[key]
+ // Note: getDay() returns 0 on Sunday
+ if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+ return value.toString().padStart(2, '0')
+ })
+ return time_str
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+ if (('' + time).length === 10) {
+ time = parseInt(time) * 1000
+ } else {
+ time = +time
+ }
+ const d = new Date(time)
+ const now = Date.now()
+
+ const diff = (now - d) / 1000
+
+ if (diff < 30) {
+ return '刚刚'
+ } else if (diff < 3600) {
+ // less 1 hour
+ return Math.ceil(diff / 60) + '分钟前'
+ } else if (diff < 3600 * 24) {
+ return Math.ceil(diff / 3600) + '小时前'
+ } else if (diff < 3600 * 24 * 2) {
+ return '1天前'
+ }
+ if (option) {
+ return parseTime(time, option)
+ } else {
+ return (
+ d.getMonth() +
+ 1 +
+ '月' +
+ d.getDate() +
+ '日' +
+ d.getHours() +
+ '时' +
+ d.getMinutes() +
+ '分'
+ )
+ }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+ const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+ if (!search) {
+ return {}
+ }
+ const obj = {}
+ const searchArr = search.split('&')
+ searchArr.forEach(v => {
+ const index = v.indexOf('=')
+ if (index !== -1) {
+ const name = v.substring(0, index)
+ const val = v.substring(index + 1, v.length)
+ obj[name] = val
+ }
+ })
+ return obj
+}
diff --git a/vue-gg-market/src/utils/request.js b/vue-gg-market/src/utils/request.js
new file mode 100644
index 0000000..608d615
--- /dev/null
+++ b/vue-gg-market/src/utils/request.js
@@ -0,0 +1,85 @@
+import axios from 'axios'
+import { MessageBox, Message } from 'element-ui'
+import store from '@/store'
+import { getToken } from '@/utils/auth'
+
+// create an axios instance
+const service = axios.create({
+ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+ // withCredentials: true, // send cookies when cross-domain requests
+ timeout: 50000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+ config => {
+ // do something before request is sent
+
+ if (store.getters.token) {
+ // let each request carry token
+ // ['X-Token'] is a custom headers key
+ // please modify it according to the actual situation
+ config.headers['token'] = getToken()
+ }
+ return config
+ },
+ error => {
+ // do something with request error
+ console.log(error) // for debug
+ return Promise.reject(error)
+ }
+)
+
+// response interceptor
+service.interceptors.response.use(
+ /**
+ * If you want to get http information such as headers or status
+ * Please return response => response
+ */
+
+ /**
+ * Determine the request status by custom code
+ * Here is just an example
+ * You can also judge the status by HTTP Status Code
+ */
+ response => {
+ const res = response.data
+
+ // if the custom code is not 20000, it is judged as an error.
+ if (res.code !== 200) {
+ Message({
+ message: res.msg || 'Error',
+ type: 'error',
+ duration: 5 * 1000
+ })
+
+ // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
+ if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
+ // to re-login
+ MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
+ confirmButtonText: 'Re-Login',
+ cancelButtonText: 'Cancel',
+ type: 'warning'
+ }).then(() => {
+ store.dispatch('user/resetToken').then(() => {
+ location.reload()
+ })
+ })
+ }
+ return Promise.reject(new Error(res.msg || 'Error'))
+ } else {
+ return res
+ }
+ },
+ error => {
+ console.log('err' + error) // for debug
+ Message({
+ message: error.msg,
+ type: 'error',
+ duration: 5 * 1000
+ })
+ return Promise.reject(error)
+ }
+)
+
+export default service
diff --git a/vue-gg-market/src/utils/validate.js b/vue-gg-market/src/utils/validate.js
new file mode 100644
index 0000000..8d962ad
--- /dev/null
+++ b/vue-gg-market/src/utils/validate.js
@@ -0,0 +1,20 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+ return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+ const valid_map = ['admin', 'editor']
+ return valid_map.indexOf(str.trim()) >= 0
+}
diff --git a/vue-gg-market/src/views/404.vue b/vue-gg-market/src/views/404.vue
new file mode 100644
index 0000000..1791f55
--- /dev/null
+++ b/vue-gg-market/src/views/404.vue
@@ -0,0 +1,228 @@
+
+
+
+
+
+
OOPS!
+
+
{{ message }}
+
Please check that the URL you entered is correct, or click the button below to return to the homepage.
+
Back to home
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/dashboard/index.vue b/vue-gg-market/src/views/dashboard/index.vue
new file mode 100644
index 0000000..33e5ab6
--- /dev/null
+++ b/vue-gg-market/src/views/dashboard/index.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/form/index.vue b/vue-gg-market/src/views/form/index.vue
new file mode 100644
index 0000000..f4d66d3
--- /dev/null
+++ b/vue-gg-market/src/views/form/index.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Cancel
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/login/index.vue b/vue-gg-market/src/views/login/index.vue
new file mode 100644
index 0000000..066281d
--- /dev/null
+++ b/vue-gg-market/src/views/login/index.vue
@@ -0,0 +1,217 @@
+
+
+
+
+
+
Login Form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登 录
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu1/index.vue b/vue-gg-market/src/views/nested/menu1/index.vue
new file mode 100644
index 0000000..30cb670
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu1/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu1/menu1-1/index.vue b/vue-gg-market/src/views/nested/menu1/menu1-1/index.vue
new file mode 100644
index 0000000..27e173a
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu1/menu1-1/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu1/menu1-2/index.vue b/vue-gg-market/src/views/nested/menu1/menu1-2/index.vue
new file mode 100644
index 0000000..0c86276
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu1/menu1-2/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue b/vue-gg-market/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
new file mode 100644
index 0000000..f87d88f
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue b/vue-gg-market/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
new file mode 100644
index 0000000..d88789f
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu1/menu1-3/index.vue b/vue-gg-market/src/views/nested/menu1/menu1-3/index.vue
new file mode 100644
index 0000000..f7cd073
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu1/menu1-3/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/vue-gg-market/src/views/nested/menu2/index.vue b/vue-gg-market/src/views/nested/menu2/index.vue
new file mode 100644
index 0000000..19dd48f
--- /dev/null
+++ b/vue-gg-market/src/views/nested/menu2/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/vue-gg-market/src/views/table/index.vue b/vue-gg-market/src/views/table/index.vue
new file mode 100644
index 0000000..a1ed847
--- /dev/null
+++ b/vue-gg-market/src/views/table/index.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+ {{ scope.$index }}
+
+
+
+
+ {{ scope.row.title }}
+
+
+
+
+ {{ scope.row.author }}
+
+
+
+
+ {{ scope.row.pageviews }}
+
+
+
+
+ {{ scope.row.status }}
+
+
+
+
+
+ {{ scope.row.display_time }}
+
+
+
+
+
+
+
diff --git a/vue-gg-market/src/views/tree/index.vue b/vue-gg-market/src/views/tree/index.vue
new file mode 100644
index 0000000..89c6b01
--- /dev/null
+++ b/vue-gg-market/src/views/tree/index.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-gg-market/tests/unit/.eslintrc.js b/vue-gg-market/tests/unit/.eslintrc.js
new file mode 100644
index 0000000..958d51b
--- /dev/null
+++ b/vue-gg-market/tests/unit/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ env: {
+ jest: true
+ }
+}
diff --git a/vue-gg-market/tests/unit/components/Breadcrumb.spec.js b/vue-gg-market/tests/unit/components/Breadcrumb.spec.js
new file mode 100644
index 0000000..1d94c8f
--- /dev/null
+++ b/vue-gg-market/tests/unit/components/Breadcrumb.spec.js
@@ -0,0 +1,98 @@
+import { mount, createLocalVue } from '@vue/test-utils'
+import VueRouter from 'vue-router'
+import ElementUI from 'element-ui'
+import Breadcrumb from '@/components/Breadcrumb/index.vue'
+
+const localVue = createLocalVue()
+localVue.use(VueRouter)
+localVue.use(ElementUI)
+
+const routes = [
+ {
+ path: '/',
+ name: 'home',
+ children: [{
+ path: 'dashboard',
+ name: 'dashboard'
+ }]
+ },
+ {
+ path: '/menu',
+ name: 'menu',
+ children: [{
+ path: 'menu1',
+ name: 'menu1',
+ meta: { title: 'menu1' },
+ children: [{
+ path: 'menu1-1',
+ name: 'menu1-1',
+ meta: { title: 'menu1-1' }
+ },
+ {
+ path: 'menu1-2',
+ name: 'menu1-2',
+ redirect: 'noredirect',
+ meta: { title: 'menu1-2' },
+ children: [{
+ path: 'menu1-2-1',
+ name: 'menu1-2-1',
+ meta: { title: 'menu1-2-1' }
+ },
+ {
+ path: 'menu1-2-2',
+ name: 'menu1-2-2'
+ }]
+ }]
+ }]
+ }]
+
+const router = new VueRouter({
+ routes
+})
+
+describe('Breadcrumb.vue', () => {
+ const wrapper = mount(Breadcrumb, {
+ localVue,
+ router
+ })
+ it('dashboard', () => {
+ router.push('/dashboard')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(1)
+ })
+ it('normal route', () => {
+ router.push('/menu/menu1')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(2)
+ })
+ it('nested route', () => {
+ router.push('/menu/menu1/menu1-2/menu1-2-1')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(4)
+ })
+ it('no meta.title', () => {
+ router.push('/menu/menu1/menu1-2/menu1-2-2')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(3)
+ })
+ // it('click link', () => {
+ // router.push('/menu/menu1/menu1-2/menu1-2-2')
+ // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
+ // const second = breadcrumbArray.at(1)
+ // console.log(breadcrumbArray)
+ // const href = second.find('a').attributes().href
+ // expect(href).toBe('#/menu/menu1')
+ // })
+ // it('noRedirect', () => {
+ // router.push('/menu/menu1/menu1-2/menu1-2-1')
+ // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
+ // const redirectBreadcrumb = breadcrumbArray.at(2)
+ // expect(redirectBreadcrumb.contains('a')).toBe(false)
+ // })
+ it('last breadcrumb', () => {
+ router.push('/menu/menu1/menu1-2/menu1-2-1')
+ const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
+ const redirectBreadcrumb = breadcrumbArray.at(3)
+ expect(redirectBreadcrumb.contains('a')).toBe(false)
+ })
+})
diff --git a/vue-gg-market/tests/unit/components/Hamburger.spec.js b/vue-gg-market/tests/unit/components/Hamburger.spec.js
new file mode 100644
index 0000000..01ea303
--- /dev/null
+++ b/vue-gg-market/tests/unit/components/Hamburger.spec.js
@@ -0,0 +1,18 @@
+import { shallowMount } from '@vue/test-utils'
+import Hamburger from '@/components/Hamburger/index.vue'
+describe('Hamburger.vue', () => {
+ it('toggle click', () => {
+ const wrapper = shallowMount(Hamburger)
+ const mockFn = jest.fn()
+ wrapper.vm.$on('toggleClick', mockFn)
+ wrapper.find('.hamburger').trigger('click')
+ expect(mockFn).toBeCalled()
+ })
+ it('prop isActive', () => {
+ const wrapper = shallowMount(Hamburger)
+ wrapper.setProps({ isActive: true })
+ expect(wrapper.contains('.is-active')).toBe(true)
+ wrapper.setProps({ isActive: false })
+ expect(wrapper.contains('.is-active')).toBe(false)
+ })
+})
diff --git a/vue-gg-market/tests/unit/components/SvgIcon.spec.js b/vue-gg-market/tests/unit/components/SvgIcon.spec.js
new file mode 100644
index 0000000..31467a9
--- /dev/null
+++ b/vue-gg-market/tests/unit/components/SvgIcon.spec.js
@@ -0,0 +1,22 @@
+import { shallowMount } from '@vue/test-utils'
+import SvgIcon from '@/components/SvgIcon/index.vue'
+describe('SvgIcon.vue', () => {
+ it('iconClass', () => {
+ const wrapper = shallowMount(SvgIcon, {
+ propsData: {
+ iconClass: 'test'
+ }
+ })
+ expect(wrapper.find('use').attributes().href).toBe('#icon-test')
+ })
+ it('className', () => {
+ const wrapper = shallowMount(SvgIcon, {
+ propsData: {
+ iconClass: 'test'
+ }
+ })
+ expect(wrapper.classes().length).toBe(1)
+ wrapper.setProps({ className: 'test' })
+ expect(wrapper.classes().includes('test')).toBe(true)
+ })
+})
diff --git a/vue-gg-market/tests/unit/utils/formatTime.spec.js b/vue-gg-market/tests/unit/utils/formatTime.spec.js
new file mode 100644
index 0000000..24e165b
--- /dev/null
+++ b/vue-gg-market/tests/unit/utils/formatTime.spec.js
@@ -0,0 +1,30 @@
+import { formatTime } from '@/utils/index.js'
+
+describe('Utils:formatTime', () => {
+ const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+ const retrofit = 5 * 1000
+
+ it('ten digits timestamp', () => {
+ expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
+ })
+ it('test now', () => {
+ expect(formatTime(+new Date() - 1)).toBe('刚刚')
+ })
+ it('less two minute', () => {
+ expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
+ })
+ it('less two hour', () => {
+ expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
+ })
+ it('less one day', () => {
+ expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
+ })
+ it('more than one day', () => {
+ expect(formatTime(d)).toBe('7月13日17时54分')
+ })
+ it('format', () => {
+ expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+ expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+ expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+ })
+})
diff --git a/vue-gg-market/tests/unit/utils/param2Obj.spec.js b/vue-gg-market/tests/unit/utils/param2Obj.spec.js
new file mode 100644
index 0000000..e106ed8
--- /dev/null
+++ b/vue-gg-market/tests/unit/utils/param2Obj.spec.js
@@ -0,0 +1,14 @@
+import { param2Obj } from '@/utils/index.js'
+describe('Utils:param2Obj', () => {
+ const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
+
+ it('param2Obj test', () => {
+ expect(param2Obj(url)).toEqual({
+ name: 'bill',
+ age: '29',
+ sex: '1',
+ field: window.btoa('test'),
+ key: '测试'
+ })
+ })
+})
diff --git a/vue-gg-market/tests/unit/utils/parseTime.spec.js b/vue-gg-market/tests/unit/utils/parseTime.spec.js
new file mode 100644
index 0000000..56045af
--- /dev/null
+++ b/vue-gg-market/tests/unit/utils/parseTime.spec.js
@@ -0,0 +1,35 @@
+import { parseTime } from '@/utils/index.js'
+
+describe('Utils:parseTime', () => {
+ const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+ it('timestamp', () => {
+ expect(parseTime(d)).toBe('2018-07-13 17:54:01')
+ })
+ it('timestamp string', () => {
+ expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
+ })
+ it('ten digits timestamp', () => {
+ expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
+ })
+ it('new Date', () => {
+ expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
+ })
+ it('format', () => {
+ expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+ expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+ expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+ })
+ it('get the day of the week', () => {
+ expect(parseTime(d, '{a}')).toBe('五') // 星期五
+ })
+ it('get the day of the week', () => {
+ expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
+ })
+ it('empty argument', () => {
+ expect(parseTime()).toBeNull()
+ })
+
+ it('null', () => {
+ expect(parseTime(null)).toBeNull()
+ })
+})
diff --git a/vue-gg-market/tests/unit/utils/validate.spec.js b/vue-gg-market/tests/unit/utils/validate.spec.js
new file mode 100644
index 0000000..f774905
--- /dev/null
+++ b/vue-gg-market/tests/unit/utils/validate.spec.js
@@ -0,0 +1,17 @@
+import { validUsername, isExternal } from '@/utils/validate.js'
+
+describe('Utils:validate', () => {
+ it('validUsername', () => {
+ expect(validUsername('admin')).toBe(true)
+ expect(validUsername('editor')).toBe(true)
+ expect(validUsername('xxxx')).toBe(false)
+ })
+ it('isExternal', () => {
+ expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false)
+ expect(isExternal('/dashboard')).toBe(false)
+ expect(isExternal('./dashboard')).toBe(false)
+ expect(isExternal('dashboard')).toBe(false)
+ })
+})
diff --git a/vue-gg-market/vue.config.js b/vue-gg-market/vue.config.js
new file mode 100644
index 0000000..92bd169
--- /dev/null
+++ b/vue-gg-market/vue.config.js
@@ -0,0 +1,131 @@
+'use strict'
+const path = require('path')
+const defaultSettings = require('./src/settings.js')
+
+function resolve(dir) {
+ return path.join(__dirname, dir)
+}
+
+const name = defaultSettings.title || 'vue Admin Template' // page title
+
+// If your port is set to 80,
+// use administrator privileges to execute the command line.
+// For example, Mac: sudo npm run
+// You can change the port by the following methods:
+// port = 9528 npm run dev OR npm run dev --port = 9528
+const port = process.env.port || process.env.npm_config_port || 9528 // dev port
+
+// All configuration item explanations can be find in https://cli.vuejs.org/config/
+module.exports = {
+ /**
+ * You will need to set publicPath if you plan to deploy your site under a sub path,
+ * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
+ * then publicPath should be set to "/bar/".
+ * In most cases please use '/' !!!
+ * Detail: https://cli.vuejs.org/config/#publicpath
+ */
+ publicPath: '/',
+ outputDir: 'dist',
+ assetsDir: 'static',
+ lintOnSave: process.env.NODE_ENV === 'development',
+ productionSourceMap: false,
+ devServer: {
+ port: port,
+ open: true,
+ overlay: {
+ warnings: false,
+ errors: true
+ },
+ proxy: {
+ [process.env.VUE_APP_BASE_API]: {
+ target: 'http://127.0.0.1:18080',
+ changeOrigin: true,
+ pathRewrite: {
+ ['^' + process.env.VUE_APP_BASE_API]: ''
+ }
+ }
+ }
+ },
+ configureWebpack: {
+ // provide the app's title in webpack's name field, so that
+ // it can be accessed in index.html to inject the correct title.
+ name: name,
+ resolve: {
+ alias: {
+ '@': resolve('src')
+ }
+ }
+ },
+ chainWebpack(config) {
+ // it can improve the speed of the first screen, it is recommended to turn on preload
+ config.plugin('preload').tap(() => [
+ {
+ rel: 'preload',
+ // to ignore runtime.js
+ // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
+ fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
+ include: 'initial'
+ }
+ ])
+
+ // when there are many pages, it will cause too many meaningless requests
+ config.plugins.delete('prefetch')
+
+ // set svg-sprite-loader
+ config.module
+ .rule('svg')
+ .exclude.add(resolve('src/icons'))
+ .end()
+ config.module
+ .rule('icons')
+ .test(/\.svg$/)
+ .include.add(resolve('src/icons'))
+ .end()
+ .use('svg-sprite-loader')
+ .loader('svg-sprite-loader')
+ .options({
+ symbolId: 'icon-[name]'
+ })
+ .end()
+
+ config
+ .when(process.env.NODE_ENV !== 'development',
+ config => {
+ config
+ .plugin('ScriptExtHtmlWebpackPlugin')
+ .after('html')
+ .use('script-ext-html-webpack-plugin', [{
+ // `runtime` must same as runtimeChunk name. default is `runtime`
+ inline: /runtime\..*\.js$/
+ }])
+ .end()
+ config
+ .optimization.splitChunks({
+ chunks: 'all',
+ cacheGroups: {
+ libs: {
+ name: 'chunk-libs',
+ test: /[\\/]node_modules[\\/]/,
+ priority: 10,
+ chunks: 'initial' // only package third parties that are initially dependent
+ },
+ elementUI: {
+ name: 'chunk-elementUI', // split elementUI into a single package
+ priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
+ test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
+ },
+ commons: {
+ name: 'chunk-commons',
+ test: resolve('src/components'), // can customize your rules
+ minChunks: 3, // minimum common number
+ priority: 5,
+ reuseExistingChunk: true
+ }
+ }
+ })
+ // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
+ config.optimization.runtimeChunk('single')
+ }
+ )
+ }
+}
diff --git a/work-auth/pom.xml b/work-auth/pom.xml
new file mode 100644
index 0000000..352067a
--- /dev/null
+++ b/work-auth/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ com.bwie
+ GG-market
+ 1.0-SNAPSHOT
+
+
+ work-auth
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ com.bwie
+ work-common
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
diff --git a/work-auth/src/main/java/com/bwie/auth/AuthApplication.java b/work-auth/src/main/java/com/bwie/auth/AuthApplication.java
new file mode 100644
index 0000000..2d14d20
--- /dev/null
+++ b/work-auth/src/main/java/com/bwie/auth/AuthApplication.java
@@ -0,0 +1,23 @@
+package com.bwie.auth;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.auth
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class AuthApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(AuthApplication.class);
+ }
+}
diff --git a/work-auth/src/main/java/com/bwie/auth/controller/AuthController.java b/work-auth/src/main/java/com/bwie/auth/controller/AuthController.java
new file mode 100644
index 0000000..bd32435
--- /dev/null
+++ b/work-auth/src/main/java/com/bwie/auth/controller/AuthController.java
@@ -0,0 +1,44 @@
+package com.bwie.auth.controller;
+
+import com.bwie.auth.service.AuthService;
+import com.bwie.common.domain.Emp;
+import com.bwie.common.domain.request.LoginRequest;
+import com.bwie.common.domain.response.JwtResponse;
+import com.bwie.common.result.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @ProjectName: cms
+ * @PackageName: com.bwie.auth.controller
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/14
+ * @Version 1.0
+ */
+@RestController
+public class AuthController {
+
+ @Autowired
+ private AuthService authService;
+
+ @PostMapping("/login")
+ public Result login(@RequestBody LoginRequest loginRequest){
+ return authService.login(loginRequest);
+ }
+
+ @GetMapping("/user/info")
+ public Result userInfo(){
+ Emp emp = authService.userInfo();
+ return Result.success(emp);
+ }
+
+ @PostMapping("/logout")
+ public Result logout(){
+ authService.logout();
+ return Result.success();
+ }
+}
diff --git a/work-auth/src/main/java/com/bwie/auth/feign/UserFeignService.java b/work-auth/src/main/java/com/bwie/auth/feign/UserFeignService.java
new file mode 100644
index 0000000..ddbae96
--- /dev/null
+++ b/work-auth/src/main/java/com/bwie/auth/feign/UserFeignService.java
@@ -0,0 +1,21 @@
+package com.bwie.auth.feign;
+
+import com.bwie.common.domain.Emp;
+import com.bwie.common.result.Result;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+/**
+ * @ProjectName: cms
+ * @PackageName: com.bwie.auth.feign
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/14
+ * @Version 1.0
+ */
+@FeignClient(name = "work-user")
+public interface UserFeignService {
+ @GetMapping("/findByName/{username}")
+ public Result findByName(@PathVariable("username") String username);
+}
diff --git a/work-auth/src/main/java/com/bwie/auth/service/AuthService.java b/work-auth/src/main/java/com/bwie/auth/service/AuthService.java
new file mode 100644
index 0000000..a4e53c0
--- /dev/null
+++ b/work-auth/src/main/java/com/bwie/auth/service/AuthService.java
@@ -0,0 +1,22 @@
+package com.bwie.auth.service;
+
+import com.bwie.common.domain.Emp;
+import com.bwie.common.domain.request.LoginRequest;
+import com.bwie.common.domain.response.JwtResponse;
+import com.bwie.common.result.Result;
+
+/**
+ * @ProjectName: cms
+ * @PackageName: com.bwie.auth.service
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/14
+ * @Version 1.0
+ */
+public interface AuthService {
+ Result login(LoginRequest loginRequest);
+
+ Emp userInfo();
+
+ void logout();
+}
diff --git a/work-auth/src/main/java/com/bwie/auth/service/impl/AuthServiceImpl.java b/work-auth/src/main/java/com/bwie/auth/service/impl/AuthServiceImpl.java
new file mode 100644
index 0000000..fd3939a
--- /dev/null
+++ b/work-auth/src/main/java/com/bwie/auth/service/impl/AuthServiceImpl.java
@@ -0,0 +1,84 @@
+package com.bwie.auth.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.bwie.auth.feign.UserFeignService;
+import com.bwie.auth.service.AuthService;
+import com.bwie.common.constants.JwtConstants;
+import com.bwie.common.constants.TokenConstants;
+import com.bwie.common.domain.Emp;
+import com.bwie.common.domain.request.LoginRequest;
+import com.bwie.common.domain.response.JwtResponse;
+import com.bwie.common.result.Result;
+import com.bwie.common.utils.JwtUtils;
+import com.bwie.common.utils.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @ProjectName: cms
+ * @PackageName: com.bwie.auth.service.impl
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/14
+ * @Version 1.0
+ */
+@Service
+public class AuthServiceImpl implements AuthService {
+
+ @Autowired
+ private UserFeignService empFeignService;
+
+ @Autowired
+ private RedisTemplate redisTemplate;
+
+ @Autowired
+ private HttpServletRequest request;
+
+ @Override
+ public Result login(LoginRequest loginRequest) {
+ if (StringUtils.isEmpty(loginRequest.getUsername()) || StringUtils.isEmpty(loginRequest.getPassword())){
+ return Result.error("账号密码不能为空");
+ }
+ Result byName = empFeignService.findByName(loginRequest.getUsername());
+ Emp loginUser = byName.getData();
+ if (null == loginUser){
+ return Result.error("该用户不存在!");
+ }
+ if (!loginUser.getPassword().equals(loginRequest.getPassword())){
+ return Result.error("密码错误");
+ }
+ String userKey = UUID.randomUUID().toString().replaceAll("-", "");
+ Map jwtMap = new HashMap<>(100, 100);
+ jwtMap.put(JwtConstants.USER_KEY, userKey);
+ jwtMap.put(JwtConstants.DETAILS_USER_ID, loginUser.getEmpId());
+ String token = JwtUtils.createToken(jwtMap);
+ redisTemplate.opsForValue().set(TokenConstants.LOGIN_TOKEN_KEY + userKey, JSONObject.toJSONString(loginUser),
+ 30, TimeUnit.MINUTES);
+ JwtResponse jwtResponse = new JwtResponse();
+ jwtResponse.setToken(token);
+ jwtResponse.setExpireTime("30MIN");
+ return Result.success(jwtResponse);
+ }
+
+ @Override
+ public Emp userInfo() {
+ String token = request.getHeader(TokenConstants.TOKEN);
+ String userKey = JwtUtils.getUserKey(token);
+ String json = redisTemplate.opsForValue().get(TokenConstants.LOGIN_TOKEN_KEY + userKey);
+ return JSONObject.parseObject(json, Emp.class);
+ }
+
+ @Override
+ public void logout() {
+ String token = request.getHeader(TokenConstants.TOKEN);
+ String userKey = JwtUtils.getUserKey(token);
+ redisTemplate.delete(TokenConstants.LOGIN_TOKEN_KEY + userKey);
+ }
+}
diff --git a/work-auth/src/main/resources/bootstrap.yml b/work-auth/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..ff09385
--- /dev/null
+++ b/work-auth/src/main/resources/bootstrap.yml
@@ -0,0 +1,35 @@
+# Tomcat
+server:
+ port: 10001
+# Spring
+spring:
+ main:
+ allow-circular-references: true
+ jackson:
+ date-format: yyyy-MM-dd HH:mm:ss
+ time-zone: GMT+8
+ application:
+ # 应用名称
+ name: work-auth
+ profiles:
+ # 环境配置
+ active: dev
+ cloud:
+ nacos:
+ discovery:
+ # 服务注册地址
+ server-addr: 192.168.73.129:8848
+ username: nacos
+ password: nacos
+ namespace: market
+ config:
+ # 配置中心地址
+ server-addr: 192.168.73.129:8848
+ username: nacos
+ password: nacos
+ namespace: market
+ # 配置文件格式
+ file-extension: yml
+ # 共享配置
+ shared-configs:
+ - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
diff --git a/work-common/pom.xml b/work-common/pom.xml
new file mode 100644
index 0000000..e35b2f2
--- /dev/null
+++ b/work-common/pom.xml
@@ -0,0 +1,146 @@
+
+
+ 4.0.0
+
+ com.bwie
+ GG-market
+ 1.0-SNAPSHOT
+
+
+ work-common
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-sentinel
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.9.1
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.80
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.3
+
+
+
+ com.aliyun
+ dysmsapi20170525
+ 2.0.1
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.2.1
+
+
+ org.apache.httpcomponents
+ httpcore
+ 4.2.1
+
+
+ commons-lang
+ commons-lang
+ 2.6
+
+
+ org.eclipse.jetty
+ jetty-util
+ 9.3.7.v20160115
+
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.9.10
+
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+
+ com.aliyun.oss
+ aliyun-sdk-oss
+ 3.12.0
+
+
+
+ com.github.tobato
+ fastdfs-client
+ 1.26.5
+
+
+
+
diff --git a/work-common/src/main/java/com/bwie/common/constants/Constants.java b/work-common/src/main/java/com/bwie/common/constants/Constants.java
new file mode 100644
index 0000000..e5f6c66
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/constants/Constants.java
@@ -0,0 +1,20 @@
+package com.bwie.common.constants;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO 系统常量
+ * @Version: 1.0
+ */
+public class Constants {
+ /**
+ * 成功标记
+ */
+ public static final Integer SUCCESS = 200;
+ public static final String SUCCESS_MSG = "操作成功";
+ /**
+ * 失败标记
+ */
+ public static final Integer ERROR = 500;
+ public static final String ERROR_MSG = "操作异常";
+}
diff --git a/work-common/src/main/java/com/bwie/common/constants/JwtConstants.java b/work-common/src/main/java/com/bwie/common/constants/JwtConstants.java
new file mode 100644
index 0000000..18acfd4
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/constants/JwtConstants.java
@@ -0,0 +1,31 @@
+package com.bwie.common.constants;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO Jwt常量
+ * @Version: 1.0
+ */
+public class JwtConstants {
+
+ /**
+ * 用户ID字段
+ */
+ public static final String DETAILS_USER_ID = "user_id";
+
+ /**
+ * 用户名字段
+ */
+ public static final String DETAILS_USERNAME = "username";
+
+ /**
+ * 用户标识
+ */
+ public static final String USER_KEY = "user_key";
+
+ /**
+ * 令牌秘钥
+ */
+ public final static String SECRET = "abcdefghijklmnopqrstuvwxyz";
+
+}
diff --git a/work-common/src/main/java/com/bwie/common/constants/TokenConstants.java b/work-common/src/main/java/com/bwie/common/constants/TokenConstants.java
new file mode 100644
index 0000000..9080624
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/constants/TokenConstants.java
@@ -0,0 +1,26 @@
+package com.bwie.common.constants;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO 令牌常量
+ * @Version: 1.0
+ */
+public class TokenConstants {
+ /**
+ * 缓存有效期,默认720(分钟)
+ */
+ public final static long EXPIRATION = 720;
+ /**
+ * 缓存刷新时间,默认120(分钟)
+ */
+ public final static long REFRESH_TIME = 120;
+ /**
+ * 权限缓存前缀
+ */
+ public final static String LOGIN_TOKEN_KEY = "login_tokens:";
+ /**
+ * token标识
+ */
+ public static final String TOKEN = "token";
+}
diff --git a/work-common/src/main/java/com/bwie/common/domain/Emp.java b/work-common/src/main/java/com/bwie/common/domain/Emp.java
new file mode 100644
index 0000000..a07b414
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/domain/Emp.java
@@ -0,0 +1,61 @@
+package com.bwie.common.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.common.domain
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@Data
+public class Emp {
+ /**
+ * 员工Id
+ */
+ private Integer empId;
+ /**
+ * 员工姓名
+ */
+ private String empName;
+ /**
+ * 员工用户名
+ */
+ private String username;
+ /**
+ * 员工密码
+ */
+ private String password;
+ /**
+ * 员工手机号
+ */
+ private String empTel;
+ /**
+ * 员工身份证号
+ */
+ private String empIdCard;
+ /**
+ * 员工年龄
+ */
+ private Integer empAge;
+ /**
+ * 员工性别:1-男 2-女
+ */
+ private Integer empGender;
+ /**
+ * 员工住址
+ */
+ private String empAddress;
+ /**
+ * 员工职位
+ */
+ private String empPosition;
+ /**
+ * 员工薪资
+ */
+ private BigDecimal empSal;
+}
diff --git a/work-common/src/main/java/com/bwie/common/domain/request/LoginRequest.java b/work-common/src/main/java/com/bwie/common/domain/request/LoginRequest.java
new file mode 100644
index 0000000..6691a14
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/domain/request/LoginRequest.java
@@ -0,0 +1,23 @@
+package com.bwie.common.domain.request;
+
+import lombok.Data;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.common.domain.request
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@Data
+public class LoginRequest {
+ /**
+ * 员工用户名
+ */
+ private String username;
+ /**
+ * 员工密码
+ */
+ private String password;
+}
diff --git a/work-common/src/main/java/com/bwie/common/domain/response/JwtResponse.java b/work-common/src/main/java/com/bwie/common/domain/response/JwtResponse.java
new file mode 100644
index 0000000..909886d
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/domain/response/JwtResponse.java
@@ -0,0 +1,23 @@
+package com.bwie.common.domain.response;
+
+import lombok.Data;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.common.domain.response
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@Data
+public class JwtResponse {
+ /**
+ * token令牌
+ */
+ private String token;
+ /**
+ * 过期时间
+ */
+ private String expireTime;
+}
diff --git a/work-common/src/main/java/com/bwie/common/result/PageResult.java b/work-common/src/main/java/com/bwie/common/result/PageResult.java
new file mode 100644
index 0000000..33a9ce4
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/result/PageResult.java
@@ -0,0 +1,36 @@
+package com.bwie.common.result;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO 列表返回结果集
+ * @Version: 1.0
+ */
+@Data
+public class PageResult implements Serializable {
+ /**
+ * 总条数
+ */
+ private long total;
+ /**
+ * 结果集合
+ */
+ private List list;
+ public PageResult() {
+ }
+ public PageResult(long total, List list) {
+ this.total = total;
+ this.list = list;
+ }
+ public static PageResult toPageResult(long total, List list){
+ return new PageResult(total , list);
+ }
+ public static Result> toResult(long total, List list){
+ return Result.success(PageResult.toPageResult(total,list));
+ }
+}
diff --git a/work-common/src/main/java/com/bwie/common/result/Result.java b/work-common/src/main/java/com/bwie/common/result/Result.java
new file mode 100644
index 0000000..f2c031a
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/result/Result.java
@@ -0,0 +1,78 @@
+package com.bwie.common.result;
+
+import com.bwie.common.constants.Constants;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO 响应信息主体
+ * @Version: 1.0
+ */
+@Data
+public class Result implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ /**
+ * 成功
+ */
+ public static final int SUCCESS = Constants.SUCCESS;
+ /**
+ * 失败
+ */
+ public static final int FAIL = Constants.ERROR;
+ /**
+ * 返回状态码
+ */
+ private int code;
+ /**
+ * 响应信息
+ */
+ private String msg;
+ /**
+ * 响应数据
+ */
+ private T data;
+
+ public static Result success() {
+ return restResult(null, SUCCESS, Constants.SUCCESS_MSG);
+ }
+
+ public static Result success(T data) {
+ return restResult(data, SUCCESS, Constants.SUCCESS_MSG);
+ }
+
+ public static Result success(T data, String msg) {
+ return restResult(data, SUCCESS, msg);
+ }
+
+ public static Result error() {
+ return restResult(null, FAIL, Constants.ERROR_MSG);
+ }
+
+ public static Result error(String msg) {
+ return restResult(null, FAIL, msg);
+ }
+
+ public static Result error(T data) {
+ return restResult(data, FAIL, Constants.ERROR_MSG);
+ }
+
+ public static Result error(T data, String msg) {
+ return restResult(data, FAIL, msg);
+ }
+
+ public static Result error(int code, String msg) {
+ return restResult(null, code, msg);
+ }
+
+ private static Result restResult(T data, int code, String msg) {
+ Result apiResult = new Result<>();
+ apiResult.setCode(code);
+ apiResult.setData(data);
+ apiResult.setMsg(msg);
+ return apiResult;
+ }
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/FastUtil.java b/work-common/src/main/java/com/bwie/common/utils/FastUtil.java
new file mode 100644
index 0000000..1d0899d
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/FastUtil.java
@@ -0,0 +1,55 @@
+package com.bwie.common.utils;
+
+import com.github.tobato.fastdfs.domain.fdfs.StorePath;
+import com.github.tobato.fastdfs.service.FastFileStorageClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.utils
+ * @Description: TODO 需要使用的微服务中放入该服务下的utils包内
+ * @Version: 1.0 121.36.202.181
+ */
+@Component
+public class FastUtil {
+ private static final Logger log = LoggerFactory.getLogger(FastUtil.class);
+
+ @Resource
+ private FastFileStorageClient storageClient ;
+
+ /**
+ * 上传文件
+ */
+ public String upload(MultipartFile multipartFile) throws Exception{
+ String originalFilename = multipartFile.getOriginalFilename().
+ substring(multipartFile.getOriginalFilename().
+ lastIndexOf(".") + 1);
+ StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(
+ multipartFile.getInputStream(),
+ multipartFile.getSize(),originalFilename , null);
+ return storePath.getFullPath() ;
+ }
+
+ /**
+ * 删除文件
+ */
+ public String deleteFile(String fileUrl) {
+ if (StringUtils.isEmpty(fileUrl)) {
+ log.info("fileUrl == >>文件路径为空...");
+ return "文件路径不能为空";
+ }
+ try {
+ StorePath storePath = StorePath.parseFromUrl(fileUrl);
+ storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ }
+ return "删除成功";
+ }
+
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/HttpUtils.java b/work-common/src/main/java/com/bwie/common/utils/HttpUtils.java
new file mode 100644
index 0000000..0dd0fd4
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/HttpUtils.java
@@ -0,0 +1,341 @@
+package com.bwie.common.utils;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Li YongJie
+ * @version 1.0.0
+ * @ClassName MsgUtil.java
+ * @Description TODO
+ * @createTime 2022年04月27日 13:53:00
+ */
+public class HttpUtils {
+
+ private static final String SCHEME = "https://";
+
+ /**
+ * get
+ *
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doGet(String host, String path, String method,
+ Map headers,
+ Map querys)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpGet request = new HttpGet(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ return httpClient.execute(request);
+ }
+
+ /**
+ * post form
+ *
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @param bodys
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doPost(String host, String path, String method,
+ Map headers,
+ Map querys,
+ Map bodys)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpPost request = new HttpPost(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ if (bodys != null) {
+ List nameValuePairList = new ArrayList();
+
+ for (String key : bodys.keySet()) {
+ nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
+ }
+ UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
+ formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
+ request.setEntity(formEntity);
+ }
+
+ return httpClient.execute(request);
+ }
+
+ /**
+ * Post String
+ *
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @param body
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doPost(String host, String path, String method,
+ Map headers,
+ Map querys,
+ String body)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpPost request = new HttpPost(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ if (StringUtils.isNotBlank(body)) {
+ request.setEntity(new StringEntity(body, "utf-8"));
+ }
+
+ return httpClient.execute(request);
+ }
+
+ /**
+ * Post stream
+ *
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @param body
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doPost(String host, String path, String method,
+ Map headers,
+ Map querys,
+ byte[] body)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpPost request = new HttpPost(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ if (body != null) {
+ request.setEntity(new ByteArrayEntity(body));
+ }
+
+ return httpClient.execute(request);
+ }
+
+ /**
+ * Put String
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @param body
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doPut(String host, String path, String method,
+ Map headers,
+ Map querys,
+ String body)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpPut request = new HttpPut(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ if (StringUtils.isNotBlank(body)) {
+ request.setEntity(new StringEntity(body, "utf-8"));
+ }
+
+ return httpClient.execute(request);
+ }
+
+ /**
+ * Put stream
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @param body
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doPut(String host, String path, String method,
+ Map headers,
+ Map querys,
+ byte[] body)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpPut request = new HttpPut(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ if (body != null) {
+ request.setEntity(new ByteArrayEntity(body));
+ }
+
+ return httpClient.execute(request);
+ }
+
+ /**
+ * Delete
+ *
+ * @param host
+ * @param path
+ * @param method
+ * @param headers
+ * @param querys
+ * @return
+ * @throws Exception
+ */
+ public static HttpResponse doDelete(String host, String path, String method,
+ Map headers,
+ Map querys)
+ throws Exception {
+ HttpClient httpClient = wrapClient(host);
+
+ HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
+ for (Map.Entry e : headers.entrySet()) {
+ request.addHeader(e.getKey(), e.getValue());
+ }
+
+ return httpClient.execute(request);
+ }
+
+ private static String buildUrl(String host, String path, Map querys) throws UnsupportedEncodingException {
+ StringBuilder sbUrl = new StringBuilder();
+ sbUrl.append(host);
+ if (!StringUtils.isBlank(path)) {
+ sbUrl.append(path);
+ }
+ if (null != querys) {
+ StringBuilder sbQuery = new StringBuilder();
+ for (Map.Entry query : querys.entrySet()) {
+ if (0 < sbQuery.length()) {
+ sbQuery.append("&");
+ }
+ if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
+ sbQuery.append(query.getValue());
+ }
+ if (!StringUtils.isBlank(query.getKey())) {
+ sbQuery.append(query.getKey());
+ if (!StringUtils.isBlank(query.getValue())) {
+ sbQuery.append("=");
+ sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
+ }
+ }
+ }
+ if (0 < sbQuery.length()) {
+ sbUrl.append("?").append(sbQuery);
+ }
+ }
+
+ return sbUrl.toString();
+ }
+
+ private static HttpClient wrapClient(String host) {
+ HttpClient httpClient = new DefaultHttpClient();
+ if (host.startsWith(SCHEME)) {
+ sslClient(httpClient);
+ }
+
+ return httpClient;
+ }
+
+ /**
+ * sslClient
+ * @param httpClient
+ */
+ private static void sslClient(HttpClient httpClient) {
+ try {
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ X509TrustManager tm = new X509TrustManager() {
+ /**
+ * getAcceptedIssuers
+ * @return
+ */
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ /**
+ * checkClientTrusted
+ * @param xcs
+ * @param str
+ */
+ @Override
+ public void checkClientTrusted(X509Certificate[] xcs, String str) {
+
+ }
+ /**
+ * checkServerTrusted
+ * @param xcs
+ * @param str
+ */
+ @Override
+ public void checkServerTrusted(X509Certificate[] xcs, String str) {
+
+ }
+ };
+ ctx.init(null, new TrustManager[] { tm }, null);
+ SSLSocketFactory ssf = new SSLSocketFactory(ctx);
+ ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+ ClientConnectionManager ccm = httpClient.getConnectionManager();
+ SchemeRegistry registry = ccm.getSchemeRegistry();
+ registry.register(new Scheme("https", 443, ssf));
+ } catch (KeyManagementException ex) {
+ throw new RuntimeException(ex);
+ } catch (NoSuchAlgorithmException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/JwtUtils.java b/work-common/src/main/java/com/bwie/common/utils/JwtUtils.java
new file mode 100644
index 0000000..4ec81ab
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/JwtUtils.java
@@ -0,0 +1,111 @@
+package com.bwie.common.utils;
+
+import com.bwie.common.constants.JwtConstants;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.Map;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO Jwt工具类
+ * @Version: 1.0
+ */
+public class JwtUtils {
+
+ /**
+ * 秘钥
+ */
+ public static String secret = JwtConstants.SECRET;
+
+ /**
+ * 从数据声明生成令牌
+ *
+ * @param claims 数据声明
+ * @return 令牌
+ */
+ public static String createToken(Map claims){
+ String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
+ return token;
+ }
+
+ /**
+ * 从令牌中获取数据声明
+ *
+ * @param token 令牌
+ * @return 数据声明
+ */
+ public static Claims parseToken(String token){
+ return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
+ }
+ /**
+ * 根据令牌获取用户标识
+ *
+ * @param token 令牌
+ * @return 用户ID
+ */
+ public static String getUserKey(String token){
+ Claims claims = parseToken(token);
+ return getValue(claims, JwtConstants.USER_KEY);
+ }
+ /**
+ * 根据令牌获取用户标识
+ *
+ * @param claims 身份信息
+ * @return 用户ID
+ */
+ public static String getUserKey(Claims claims){
+ return getValue(claims, JwtConstants.USER_KEY);
+ }
+ /**
+ * 根据令牌获取用户ID
+ *
+ * @param token 令牌
+ * @return 用户ID
+ */
+ public static String getUserId(String token){
+ Claims claims = parseToken(token);
+ return getValue(claims, JwtConstants.DETAILS_USER_ID);
+ }
+ /**
+ * 根据身份信息获取用户ID
+ *
+ * @param claims 身份信息
+ * @return 用户ID
+ */
+ public static String getUserId(Claims claims){
+ return getValue(claims, JwtConstants.DETAILS_USER_ID);
+ }
+ /**
+ * 根据令牌获取用户名
+ *
+ * @param token 令牌
+ * @return 用户名
+ */
+ public static String getUserName(String token){
+ Claims claims = parseToken(token);
+ return getValue(claims, JwtConstants.DETAILS_USERNAME);
+ }
+ /**
+ * 根据身份信息获取用户名
+ *
+ * @param claims 身份信息
+ * @return 用户名
+ */
+ public static String getUserName(Claims claims){
+ return getValue(claims, JwtConstants.DETAILS_USERNAME);
+ }
+ /**
+ * 根据身份信息获取键值
+ *
+ * @param claims 身份信息
+ * @param key 键
+ * @return 值
+ */
+ public static String getValue(Claims claims, String key){
+ Object obj = claims.get(key);
+ return obj == null ? "" : obj.toString();
+ }
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/MsgUtil.java b/work-common/src/main/java/com/bwie/common/utils/MsgUtil.java
new file mode 100644
index 0000000..6db5add
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/MsgUtil.java
@@ -0,0 +1,55 @@
+package com.bwie.common.utils;
+
+import org.apache.http.HttpResponse;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Li YongJie
+ * @version 1.0.0
+ * @ClassName MsgUtil.java
+ * @Description TODO
+ * @createTime 2022年04月27日 13:53:00
+ */
+public class MsgUtil {
+
+
+ public static void sendMsg(String phone,String code){
+
+ String host = "http://dingxin.market.alicloudapi.com";
+ String path = "/dx/sendSms";
+ String method = "POST";
+ String appcode = "fe89da4e62954ebda78bd257785e6a53";
+ Map headers = new HashMap(200,200);
+ //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 86873be959334103b775996a22409375
+ headers.put("Authorization", "APPCODE " + appcode);
+ Map querys = new HashMap(200,200);
+ querys.put("mobile", phone);
+ querys.put("param", "code:"+code);
+ //模板不允许改动。 如果要改动,联系客服
+ querys.put("tpl_id", "TP1711063");
+ Map bodys = new HashMap(200,200);
+
+
+ try {
+ /**
+ * 重要提示如下:
+ * HttpUtils请从
+ * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
+ * 下载
+ *
+ * 相应的依赖请参照
+ * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
+ *
+ *
+ * 获取response的body
+ * EntityUtils.toString(response.getEntity())
+ */
+ HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
+ System.out.println(response.toString());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/OssUtil.java b/work-common/src/main/java/com/bwie/common/utils/OssUtil.java
new file mode 100644
index 0000000..9d96152
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/OssUtil.java
@@ -0,0 +1,156 @@
+package com.bwie.common.utils;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.GetObjectRequest;
+import com.aliyun.oss.model.PutObjectRequest;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.utils
+ * @Description: TODO Oss服务调用
+ * @Version: 1.0
+ */
+@Log4j2
+public class OssUtil {
+
+ /**
+ * Endpoint 存储对象概述 阿里云主账号AccessKey,accessKeySecret拥有所有API的访问权限 访问路径前缀 存储对象概述
+ */
+ private static String endPoint = "oss-cn-shanghai.aliyuncs.com";// 访问域名
+ private static String accessKeyId = "LTAI5tKXvCvwq7hKpZgsY2hk";//密钥id
+ private static String accessKeySecret = "HaOTCePEpeLNukQL3jhR6DZu9CAnmA";// 密钥值
+ private static String accessPre = "https://arklorse.oss-cn-shanghai.aliyuncs.com/";//服务前缀URL
+
+ /**
+ * bucket名称
+ * @return
+ */
+ private static String bucketName = "arklorse";
+
+ private static OSS ossClient ;
+
+ static {
+ ossClient = new OSSClientBuilder().build(
+ endPoint,
+ accessKeyId,
+ accessKeySecret);
+ log.info("oss服务连接成功!");
+ }
+
+ /**
+ * 默认路径上传本地文件
+ * @param filePath
+ */
+ public static String uploadFile(String filePath){
+ return uploadFileForBucket(bucketName,getOssFilePath(filePath) ,filePath);
+ }
+
+ /**
+ * 默认路径上传multipartFile文件
+ * @param multipartFile
+ */
+ public static String uploadMultipartFile(MultipartFile multipartFile) {
+ return uploadMultipartFile(bucketName,getOssFilePath(multipartFile.getOriginalFilename()),multipartFile);
+ }
+ /**
+ * 上传 multipartFile 类型文件
+ * @param bucketName
+ * @param ossPath
+ * @param multipartFile
+ */
+ public static String uploadMultipartFile(String bucketName , String ossPath , MultipartFile multipartFile){
+ InputStream inputStream = null;
+ try {
+ inputStream = multipartFile.getInputStream();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ uploadFileInputStreamForBucket(bucketName, ossPath, inputStream);
+ return accessPre+ossPath;
+ }
+
+ /**
+ * 使用File上传PutObject上传文件 ** 程序默认使用次方法上传
+ * @param bucketName 实例名称
+ * @param ossPath oss存储路径
+ * @param filePath 本地文件路径
+ */
+ public static String uploadFileForBucket(String bucketName , String ossPath , String filePath) {
+ // 创建PutObjectRequest对象。
+ PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, ossPath, new File(filePath));
+
+ // 上传
+ ossClient.putObject(putObjectRequest);
+ return accessPre+ossPath;
+ }
+
+ /**
+ * 使用文件流上传到指定的bucket实例
+ * @param bucketName 实例名称
+ * @param ossPath oss存储路径
+ * @param filePath 本地文件路径
+ */
+ public static String uploadFileInputStreamForBucket(String bucketName , String ossPath , String filePath){
+
+ // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
+ InputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(filePath);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ // 填写Bucket名称和Object完整路径。Object完整路径中不能包含Bucket名称。
+ uploadFileInputStreamForBucket(bucketName, ossPath, inputStream);
+ return accessPre+ossPath;
+ }
+
+ public static void uploadFileInputStreamForBucket(String bucketName , String ossPath , InputStream inputStream ){
+ ossClient.putObject(bucketName, ossPath, inputStream);
+ }
+
+ /**
+ * 下载
+ * @param ossFilePath
+ * @param filePath
+ */
+ public static void downloadFile(String ossFilePath , String filePath ){
+ downloadFileForBucket(bucketName , ossFilePath , filePath);
+ }
+ /**
+ * 下载
+ * @param bucketName 实例名称
+ * @param ossFilePath oss存储路径
+ * @param filePath 本地文件路径
+ */
+ public static void downloadFileForBucket(String bucketName , String ossFilePath , String filePath ){
+ ossClient.getObject(new GetObjectRequest(bucketName, ossFilePath), new File(filePath));
+ }
+
+ /**
+ *
+ * @return
+ */
+ public static String getOssDefaultPath(){
+ LocalDateTime now = LocalDateTime.now();
+ String url =
+ now.getYear()+"/"+
+ now.getMonth()+"/"+
+ now.getDayOfMonth()+"/"+
+ now.getHour()+"/"+
+ now.getMinute()+"/";
+ return url;
+ }
+
+ public static String getOssFilePath(String filePath){
+ String fileSuf = filePath.substring(filePath.indexOf(".") + 1);
+ return getOssDefaultPath() + UUID.randomUUID().toString() + "." + fileSuf;
+ }
+
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/StringUtils.java b/work-common/src/main/java/com/bwie/common/utils/StringUtils.java
new file mode 100644
index 0000000..b7206c6
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/StringUtils.java
@@ -0,0 +1,70 @@
+package com.bwie.common.utils;
+
+import org.springframework.util.AntPathMatcher;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO 字符串处理工具类
+ * @Version: 1.0
+ */
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+ /**
+ * * 判断一个对象是否为空
+ *
+ * @param object Object
+ * @return true:为空 false:非空
+ */
+ public static boolean isNull(Object object) {
+ return object == null;
+ }
+
+ /**
+ * * 判断一个Collection是否为空, 包含List,Set,Queue
+ *
+ * @param coll 要判断的Collection
+ * @return true:为空 false:非空
+ */
+ public static boolean isEmpty(Collection> coll) {
+ return isNull(coll) || coll.isEmpty();
+ }
+
+ /**
+ * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
+ *
+ * @param str 指定字符串
+ * @param strs 需要检查的字符串数组
+ * @return 是否匹配
+ */
+ public static boolean matches(String str, List strs) {
+ if (isEmpty(str) || isEmpty(strs)) {
+ return false;
+ }
+ for (String pattern : strs) {
+ if (isMatch(pattern, str))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断url是否与规则配置:
+ * ? 表示单个字符;
+ * * 表示一层路径内的任意字符串,不可跨层级;
+ * ** 表示任意层路径;
+ *
+ * @param pattern 匹配规则
+ * @param url 需要匹配的url
+ * @return
+ */
+ public static boolean isMatch(String pattern, String url) {
+ AntPathMatcher matcher = new AntPathMatcher();
+ return matcher.match(pattern, url);
+ }
+}
diff --git a/work-common/src/main/java/com/bwie/common/utils/TelSmsUtils.java b/work-common/src/main/java/com/bwie/common/utils/TelSmsUtils.java
new file mode 100644
index 0000000..739dcfc
--- /dev/null
+++ b/work-common/src/main/java/com/bwie/common/utils/TelSmsUtils.java
@@ -0,0 +1,90 @@
+package com.bwie.common.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+import lombok.extern.log4j.Log4j2;
+
+import java.util.Map;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.common.domain
+ * @Description: TODO 短信工具类
+ * @Version: 1.0
+ */
+@Log4j2
+public class TelSmsUtils {
+
+ /**
+ * 阿里云主账号AccessKey,accessKeySecret拥有所有API的访问权限
+ */
+ private static String accessKeyId = "LTAIEVXszCmcd1T5";
+ private static String accessKeySecret = "2zHwciQXln8wExSEnkIYtRTSwLeRNd";
+
+ /**
+ * 短信访问域名
+ */
+ private static String endpoint = "dysmsapi.aliyuncs.com";
+ /**
+ * 短信签名
+ */
+ private static String signName = "登录验证";
+
+ /**
+ * 实例化短信对象
+ */
+ private static Client client;
+
+ static {
+ log.info("初始化短信服务开始");
+ long startTime = System.currentTimeMillis();
+ try {
+ client = initClient();
+ log.info("初始化短信成功:{}",signName);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ log.info("初始化短信服务结束:耗时:{}MS",(System.currentTimeMillis()-startTime));
+ }
+ /**
+ * 初始化短信对象
+ * @return
+ * @throws Exception
+ */
+ private static Client initClient() throws Exception{
+ Config config = new Config()
+ // 您的AccessKey ID
+ .setAccessKeyId(accessKeyId)
+ // 您的AccessKey Secret
+ .setAccessKeySecret(accessKeySecret);
+ // 访问的域名
+ config.endpoint = endpoint;
+ return new Client(config);
+ }
+
+ /**
+ * 发送单条短信
+ * @param tel
+ * @param templateCode SMS_153991546
+ * @param sendDataMap
+ */
+ public static String sendSms(String tel , String templateCode , Map sendDataMap){
+ SendSmsRequest sendSmsRequest = new SendSmsRequest()
+ .setPhoneNumbers(tel)
+ .setSignName(signName)
+ .setTemplateCode(templateCode)
+ .setTemplateParam(JSONObject.toJSONString(sendDataMap));
+ SendSmsResponse sendSmsResponse = null;
+ try {
+ log.info("发送短信验证码:消息内容是:【{}】", JSONObject.toJSONString(sendDataMap));
+ sendSmsResponse = client.sendSms(sendSmsRequest);
+ } catch (Exception e) {
+ log.error("短信发送异常,手机号:【{}】,短信内容:【{}】,异常信息:【{}】", tel, sendDataMap, e);
+ }
+ return JSONObject.toJSONString(sendSmsResponse.getBody());
+ }
+
+}
diff --git a/work-gateway/pom.xml b/work-gateway/pom.xml
new file mode 100644
index 0000000..edaae5f
--- /dev/null
+++ b/work-gateway/pom.xml
@@ -0,0 +1,44 @@
+
+
+ 4.0.0
+
+ com.bwie
+ GG-market
+ 1.0-SNAPSHOT
+
+
+ work-gateway
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ com.bwie
+ work-common
+ 1.0-SNAPSHOT
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-sentinel-gateway
+
+
+
+ com.alibaba.csp
+ sentinel-spring-cloud-gateway-adapter
+
+
+
diff --git a/work-gateway/src/main/java/com/bwie/gateway/GatewayApplication.java b/work-gateway/src/main/java/com/bwie/gateway/GatewayApplication.java
new file mode 100644
index 0000000..022c496
--- /dev/null
+++ b/work-gateway/src/main/java/com/bwie/gateway/GatewayApplication.java
@@ -0,0 +1,21 @@
+package com.bwie.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.gateway
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class GatewayApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(GatewayApplication.class);
+ }
+}
diff --git a/work-gateway/src/main/java/com/bwie/gateway/config/IgnoreWhiteConfig.java b/work-gateway/src/main/java/com/bwie/gateway/config/IgnoreWhiteConfig.java
new file mode 100644
index 0000000..fabcea4
--- /dev/null
+++ b/work-gateway/src/main/java/com/bwie/gateway/config/IgnoreWhiteConfig.java
@@ -0,0 +1,34 @@
+package com.bwie.gateway.config;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.gateway
+ * @Description: TODO 放行白名单配置
+ * @Version: 1.0
+ */
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "ignore")
+@Data
+@Log4j2
+public class IgnoreWhiteConfig {
+ /**
+ * 放行白名单配置,网关不校验此处的白名单
+ */
+ private List whites = new ArrayList<>();
+
+ public void setWhites(List whites) {
+ log.info("加载网关路径白名单:{}", JSONObject.toJSONString(whites));
+ this.whites = whites;
+ }
+}
diff --git a/work-gateway/src/main/java/com/bwie/gateway/filters/AuthFilter.java b/work-gateway/src/main/java/com/bwie/gateway/filters/AuthFilter.java
new file mode 100644
index 0000000..166bfbd
--- /dev/null
+++ b/work-gateway/src/main/java/com/bwie/gateway/filters/AuthFilter.java
@@ -0,0 +1,68 @@
+package com.bwie.gateway.filters;
+
+import com.bwie.common.constants.TokenConstants;
+import com.bwie.common.utils.JwtUtils;
+import com.bwie.common.utils.StringUtils;
+import com.bwie.gateway.config.IgnoreWhiteConfig;
+import com.bwie.gateway.utils.GatewayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @PackageName: com.bwie.gateway.filters
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@Component
+public class AuthFilter implements GlobalFilter, Ordered {
+
+ @Autowired
+ private IgnoreWhiteConfig ignoreWhiteConfig;
+
+ @Autowired
+ private RedisTemplate redisTemplate;
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ ServerHttpRequest request = exchange.getRequest();
+ String path = request.getURI().getPath();
+ List whites = ignoreWhiteConfig.getWhites();
+ if (StringUtils.matches(path,whites)){
+ return chain.filter(exchange);
+ }
+ String token = request.getHeaders().getFirst(TokenConstants.TOKEN);
+ if (StringUtils.isEmpty(token)){
+ return GatewayUtils.errorResponse(exchange,"token不能为空");
+ }
+ try {
+ JwtUtils.parseToken(token);
+ } catch (Exception e) {
+ return GatewayUtils.errorResponse(exchange,"token不合法");
+ }
+ String userKey = JwtUtils.getUserKey(token);
+ Boolean aBoolean = redisTemplate.hasKey(TokenConstants.LOGIN_TOKEN_KEY + userKey);
+ if (!aBoolean){
+ return GatewayUtils.errorResponse(exchange,"token过期");
+ }else {
+ redisTemplate.expire(TokenConstants.LOGIN_TOKEN_KEY + userKey,30, TimeUnit.MINUTES);
+ }
+ return chain.filter(exchange);
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+}
diff --git a/work-gateway/src/main/java/com/bwie/gateway/utils/GatewayUtils.java b/work-gateway/src/main/java/com/bwie/gateway/utils/GatewayUtils.java
new file mode 100644
index 0000000..44298e9
--- /dev/null
+++ b/work-gateway/src/main/java/com/bwie/gateway/utils/GatewayUtils.java
@@ -0,0 +1,100 @@
+package com.bwie.gateway.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.bwie.common.result.Result;
+import com.bwie.common.utils.StringUtils;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * @Author: Li YongJie
+ * @BelongsPackage: com.bwie.gateway
+ * @Description: TODO 网关处理工具类
+ * @Version: 1.0
+ */
+@Log4j2
+public class GatewayUtils {
+ /**
+ * 添加请求头参数
+ * @param mutate 修改对象
+ * @param key 键
+ * @param value 值
+ */
+ public static void addHeader(ServerHttpRequest.Builder mutate, String key, Object value) {
+ if (StringUtils.isEmpty(key)){
+ log.warn("添加请求头参数键不可以为空");
+ return;
+ }
+ if (value == null) {
+ log.warn("添加请求头参数:[{}]值为空",key);
+ return;
+ }
+ String valueStr = value.toString();
+ mutate.header(key, valueStr);
+ log.info("添加请求头参数成功 - 键:[{}] , 值:[{}]", key , value);
+ }
+
+ /**
+ * 删除请求头参数
+ * @param mutate 修改对象
+ * @param key 键
+ */
+ public static void removeHeader(ServerHttpRequest.Builder mutate, String key) {
+ if (StringUtils.isEmpty(key)){
+ log.warn("删除请求头参数键不可以为空");
+ return;
+ }
+ mutate.headers(httpHeaders -> httpHeaders.remove(key)).build();
+ log.info("删除请求头参数 - 键:[{}]",key);
+ }
+
+ /**
+ * 错误结果响应
+ * @param exchange 响应上下文
+ * @param msg 响应消息
+ * @return
+ */
+ public static Mono errorResponse(ServerWebExchange exchange, String msg, HttpStatus httpStatus) {
+ ServerHttpResponse response = exchange.getResponse();
+ //设置HTTP响应头状态
+ response.setStatusCode(httpStatus);
+ //设置HTTP响应头文本格式
+ response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json");
+ //定义响应内容
+ Result> result = Result.error(msg);
+ String resultJson = JSONObject.toJSONString(result);
+ log.error("[鉴权异常处理]请求路径:[{}],异常信息:[{}],响应结果:[{}]", exchange.getRequest().getPath(), msg, resultJson);
+ DataBuffer dataBuffer = response.bufferFactory().wrap(resultJson.getBytes());
+ //进行响应
+ return response.writeWith(Mono.just(dataBuffer));
+ }
+
+ /**
+ * 错误结果响应
+ * @param exchange 响应上下文
+ * @param msg 响应消息
+ * @return
+ */
+ public static Mono errorResponse(ServerWebExchange exchange, String msg) {
+ ServerHttpResponse response = exchange.getResponse();
+ //设置HTTP响应头状态
+ response.setStatusCode(HttpStatus.OK);
+ //设置HTTP响应头文本格式
+ response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json");
+ //定义响应内容
+ Result> result = Result.error(msg);
+ String resultJson = JSONObject.toJSONString(result);
+ log.error("[鉴权异常处理]请求路径:[{}],异常信息:[{}],响应结果:[{}]", exchange.getRequest().getPath(), msg, resultJson);
+ DataBuffer dataBuffer = response.bufferFactory().wrap(resultJson.getBytes());
+ //进行响应
+ return response.writeWith(Mono.just(dataBuffer));
+ }
+
+
+}
diff --git a/work-gateway/src/main/resources/bootstrap.yml b/work-gateway/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..6d966a0
--- /dev/null
+++ b/work-gateway/src/main/resources/bootstrap.yml
@@ -0,0 +1,35 @@
+# Tomcat
+server:
+ port: 18080
+# Spring
+spring:
+ application:
+ # 应用名称
+ name: work-gateway
+ profiles:
+ # 环境配置
+ active: dev
+ main:
+ # 允许使用循环引用
+ allow-circular-references: true
+ # 允许定义相同的bean对象 去覆盖原有的
+ allow-bean-definition-overriding: true
+ cloud:
+ nacos:
+ discovery:
+ # 服务注册地址
+ server-addr: 192.168.73.129:8848
+ username: nacos
+ password: nacos
+ namespace: market
+ config:
+ # 配置中心地址
+ server-addr: 192.168.73.129:8848
+ username: nacos
+ password: nacos
+ namespace: market
+ # 配置文件格式
+ file-extension: yml
+ # 共享配置
+ shared-configs:
+ - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
diff --git a/work-modules/pom.xml b/work-modules/pom.xml
new file mode 100644
index 0000000..9c447d5
--- /dev/null
+++ b/work-modules/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+ com.bwie
+ GG-market
+ 1.0-SNAPSHOT
+
+
+ work-modules
+ pom
+
+ work-user
+
+
+
+ 8
+ 8
+ UTF-8
+
+
+
diff --git a/work-modules/work-user/pom.xml b/work-modules/work-user/pom.xml
new file mode 100644
index 0000000..97cc77b
--- /dev/null
+++ b/work-modules/work-user/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ com.bwie
+ work-modules
+ 1.0-SNAPSHOT
+
+
+ work-user
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ com.bwie
+ work-common
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.8
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.2.2
+
+
+
+ com.github.pagehelper
+ pagehelper-spring-boot-starter
+ 1.4.1
+
+
+
+
diff --git a/work-modules/work-user/src/main/java/com/bwie/user/UserApplication.java b/work-modules/work-user/src/main/java/com/bwie/user/UserApplication.java
new file mode 100644
index 0000000..b548b5c
--- /dev/null
+++ b/work-modules/work-user/src/main/java/com/bwie/user/UserApplication.java
@@ -0,0 +1,21 @@
+package com.bwie.user;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.user
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class UserApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(UserApplication.class);
+ }
+}
diff --git a/work-modules/work-user/src/main/java/com/bwie/user/controller/EmpController.java b/work-modules/work-user/src/main/java/com/bwie/user/controller/EmpController.java
new file mode 100644
index 0000000..904af8a
--- /dev/null
+++ b/work-modules/work-user/src/main/java/com/bwie/user/controller/EmpController.java
@@ -0,0 +1,30 @@
+package com.bwie.user.controller;
+
+import com.bwie.common.domain.Emp;
+import com.bwie.common.result.Result;
+import com.bwie.user.service.EmpService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.user.controller
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@RestController
+public class EmpController {
+
+ @Autowired
+ private EmpService empService;
+
+ @GetMapping("/findByName/{username}")
+ public Result findByName(@PathVariable("username") String username){
+ Emp emp = empService.findByName(username);
+ return Result.success(emp);
+ }
+}
diff --git a/work-modules/work-user/src/main/java/com/bwie/user/mapper/EmpMapper.java b/work-modules/work-user/src/main/java/com/bwie/user/mapper/EmpMapper.java
new file mode 100644
index 0000000..b573463
--- /dev/null
+++ b/work-modules/work-user/src/main/java/com/bwie/user/mapper/EmpMapper.java
@@ -0,0 +1,19 @@
+package com.bwie.user.mapper;
+
+import com.bwie.common.domain.Emp;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.user.mapper
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@Mapper
+public interface EmpMapper {
+
+ Emp findByName(@Param("username") String username);
+}
diff --git a/work-modules/work-user/src/main/java/com/bwie/user/service/EmpService.java b/work-modules/work-user/src/main/java/com/bwie/user/service/EmpService.java
new file mode 100644
index 0000000..477fef5
--- /dev/null
+++ b/work-modules/work-user/src/main/java/com/bwie/user/service/EmpService.java
@@ -0,0 +1,15 @@
+package com.bwie.user.service;
+
+import com.bwie.common.domain.Emp;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.user.service
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+public interface EmpService {
+ Emp findByName(String username);
+}
diff --git a/work-modules/work-user/src/main/java/com/bwie/user/service/impl/EmpServiceImpl.java b/work-modules/work-user/src/main/java/com/bwie/user/service/impl/EmpServiceImpl.java
new file mode 100644
index 0000000..5f45286
--- /dev/null
+++ b/work-modules/work-user/src/main/java/com/bwie/user/service/impl/EmpServiceImpl.java
@@ -0,0 +1,27 @@
+package com.bwie.user.service.impl;
+
+import com.bwie.common.domain.Emp;
+import com.bwie.user.mapper.EmpMapper;
+import com.bwie.user.service.EmpService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @ProjectName: GG-market
+ * @PackageName: com.bwie.user.service.impl
+ * @Description TODO
+ * @Author LiYonJie
+ * @Date 2023/11/16
+ * @Version 1.0
+ */
+@Service
+public class EmpServiceImpl implements EmpService {
+
+ @Autowired
+ private EmpMapper empMapper;
+
+ @Override
+ public Emp findByName(String username) {
+ return empMapper.findByName(username);
+ }
+}
diff --git a/work-modules/work-user/src/main/resources/bootstrap.yml b/work-modules/work-user/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..2a5c8be
--- /dev/null
+++ b/work-modules/work-user/src/main/resources/bootstrap.yml
@@ -0,0 +1,57 @@
+# Tomcat
+server:
+ port: 10002
+# Spring
+spring:
+ main:
+ allow-circular-references: true
+ jackson:
+ date-format: yyyy-MM-dd HH:mm:ss
+ time-zone: GMT+8
+ application:
+ # 应用名称
+ name: work-user
+ profiles:
+ # 环境配置
+ active: dev
+ cloud:
+ nacos:
+ discovery:
+ # 服务注册地址
+ server-addr: 192.168.73.129:8848
+ username: nacos
+ password: nacos
+ namespace: market
+ config:
+ # 配置中心地址
+ server-addr: 192.168.73.129:8848
+ username: nacos
+ password: nacos
+ namespace: market
+ # 配置文件格式
+ file-extension: yml
+ # 共享配置
+ shared-configs:
+ - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
+fdfs:
+ # socket 连接时长
+ so-timeout: 1500
+ # 连接 tracker 服务器超时时长
+ connect-timeout: 600
+ # 这两个是你服务器的 IP 地址,注意 23000 端口也要打开,阿里云服务器记得配置安全组。tracker 要和 stroage 服务进行交流
+ tracker-list: 121.36.202.181:22122
+ web-server-url: 121.36.202.181:8888
+ pool:
+ jmx-enabled: false
+ # 生成缩略图
+ thumb-image:
+ height: 500
+ width: 500
+
+aliyun:
+ oss:
+ file:
+ bucketName: arklorse
+ endpoint: oss-cn-shanghai.aliyuncs.com
+ AccessKeyId: LTAI5tKXvCvwq7hKpZgsY2hk
+ AccessKeySecret: HaOTCePEpeLNukQL3jhR6DZu9CAnmA
diff --git a/work-modules/work-user/src/main/resources/mapper/EmpMapper.xml b/work-modules/work-user/src/main/resources/mapper/EmpMapper.xml
new file mode 100644
index 0000000..e6f2ecc
--- /dev/null
+++ b/work-modules/work-user/src/main/resources/mapper/EmpMapper.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+