diff --git a/fit2cloud-view/PENDING.md b/fit2cloud-view/PENDING.md
new file mode 100644
index 0000000000..32dbb2e0a8
--- /dev/null
+++ b/fit2cloud-view/PENDING.md
@@ -0,0 +1,17 @@
+# 功能
+
+- [x] 登录页面
+- [x] 整体布局
+- [x] 路由基础框架
+- [x] 左侧菜单
+- [x] API基础框架
+- [x] mock
+- [x] 国际化及规范
+- [x] 加载FIT2CLOUD UI
+- [ ] 权限控制
+- [ ] 完整Demo路由及页面
+ - [ ] 权限 Demo
+ - [ ] Form Demo
+ - [ ] Table Demo
+- [ ] 说明文档
+
diff --git a/fit2cloud-view/README.md b/fit2cloud-view/README.md
new file mode 100644
index 0000000000..6952e9355b
--- /dev/null
+++ b/fit2cloud-view/README.md
@@ -0,0 +1 @@
+# FIT2CLOUD 应用模板
diff --git a/fit2cloud-view/babel.config.js b/fit2cloud-view/babel.config.js
new file mode 100644
index 0000000000..e9558405fd
--- /dev/null
+++ b/fit2cloud-view/babel.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ presets: [
+ '@vue/cli-plugin-babel/preset'
+ ]
+}
diff --git a/fit2cloud-view/mock/index.js b/fit2cloud-view/mock/index.js
new file mode 100644
index 0000000000..d8d14e4a0f
--- /dev/null
+++ b/fit2cloud-view/mock/index.js
@@ -0,0 +1,54 @@
+const Mock = require('mockjs')
+const {param2Obj} = require('./utils')
+
+const user = require('./user')
+
+const mocks = [
+ ...user,
+]
+
+// 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;
+ 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/fit2cloud-view/mock/license.js b/fit2cloud-view/mock/license.js
new file mode 100644
index 0000000000..1c9e3fe157
--- /dev/null
+++ b/fit2cloud-view/mock/license.js
@@ -0,0 +1,51 @@
+const {success, error} = require("./result-holder")
+
+const licenses = {
+ valid: {
+ status: "valid",
+ license: {
+ "corporation": "xxxxxxxxxxxx",
+ "expired": "2030-07-03",
+ "licenseVersion": "v2",
+ "product": "cmp",
+ "generateTime": "1593763389356",
+ "edition": "Enterprise",
+ "count": 11
+ },
+ message: ""
+ },
+ invalid: {
+ status: "invalid",
+ license: {},
+ message: "license has invalid"
+ },
+ expired: {
+ status: "expired",
+ license: {
+ "corporation": "xxxxxxxxxxxx",
+ "expired": "2020-07-03",
+ "licenseVersion": "v2",
+ "product": "cmp",
+ "generateTime": "1593763389356",
+ "edition": "Enterprise",
+ "count": 11
+ },
+ message: "license has expired since 2020-07-03"
+ },
+}
+
+module.exports = [
+ {
+ url: '/samples/license/save',
+ type: 'post',
+ response: config => {
+ const {license} = config.body
+ const data = licenses[license];
+
+ if (!data) {
+ return success(licenses.invalid)
+ }
+ return success(data)
+ }
+ },
+]
diff --git a/fit2cloud-view/mock/mock-server.js b/fit2cloud-view/mock/mock-server.js
new file mode 100644
index 0000000000..8941ec0f80
--- /dev/null
+++ b/fit2cloud-view/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/fit2cloud-view/mock/result-holder.js b/fit2cloud-view/mock/result-holder.js
new file mode 100644
index 0000000000..66db69d87b
--- /dev/null
+++ b/fit2cloud-view/mock/result-holder.js
@@ -0,0 +1,20 @@
+class ResultHolder {
+ constructor(success, data, message) {
+ this.success = success;
+ this.data = data;
+ this.message = message;
+ }
+}
+
+const success = data => {
+ return new ResultHolder(true, data)
+}
+
+const error = message => {
+ return new ResultHolder(false, undefined, message)
+}
+
+module.exports = {
+ success,
+ error
+}
diff --git a/fit2cloud-view/mock/user-token.js b/fit2cloud-view/mock/user-token.js
new file mode 100644
index 0000000000..5fb072ea5a
--- /dev/null
+++ b/fit2cloud-view/mock/user-token.js
@@ -0,0 +1,97 @@
+const {success, error} = require("./result-holder")
+const TOKEN_KEY = "App-Token"
+
+/* 前后端分离,用Token验证登录*/
+const tokens = {
+ admin: {
+ token: 'admin-token'
+ },
+ editor: {
+ token: 'editor-token'
+ },
+ readonly: {
+ token: 'readonly-token'
+ }
+}
+
+const users = {
+ 'admin-token': {
+ id: "admin",
+ name: 'Administrator',
+ email: "admin@fit2cloud.com",
+ roles: ['admin'],
+ language: "zh-CN"
+ },
+ 'editor-token': {
+ id: "editor",
+ name: 'Editor',
+ email: "editor@fit2cloud.com",
+ roles: ['editor'],
+ language: "zh-CN"
+ },
+ 'readonly-token': {
+ id: "readonly",
+ name: 'Readonly User',
+ email: "readonly@fit2cloud.com",
+ roles: ['readonly'],
+ language: "zh-CN"
+ }
+}
+
+module.exports = [
+ // user login
+ {
+ url: '/samples/user-token/login',
+ type: 'post',
+ response: config => {
+ const {username} = config.body
+ const {token} = tokens[username];
+
+ // mock error
+ if (!token) {
+ return error("用户名或密码错误")
+ }
+ return success(token)
+ }
+ },
+
+ // get user info
+ {
+ url: '/samples/user-token/info',
+ type: 'get',
+ response: (config) => {
+ let token = config.headers[TOKEN_KEY]
+ const info = users[token]
+
+ // mock error
+ if (!info) {
+ return error("无法获取用户[" + token + "]详细信息")
+ }
+
+ return success(info)
+ }
+ },
+
+ // update user info
+ {
+ url: '/samples/user/info/update',
+ type: 'put',
+ response: config => {
+ let token = config.headers[TOKEN_KEY]
+ const {language} = config.body
+ users[token].language = language;
+
+ return success(users[token])
+ }
+ },
+
+ // user logout
+ {
+ url: '/samples/user/logout',
+ type: 'post',
+ response: () => {
+ // do something
+ return success()
+ }
+ }
+]
diff --git a/fit2cloud-view/mock/user.js b/fit2cloud-view/mock/user.js
new file mode 100644
index 0000000000..98de3df117
--- /dev/null
+++ b/fit2cloud-view/mock/user.js
@@ -0,0 +1,98 @@
+const {success, error} = require("./result-holder")
+
+/* 前后端不分离的接口,用Session验证登录*/
+let currentUser
+
+const users = {
+ admin: {
+ id: "admin",
+ name: 'Administrator',
+ email: "admin@fit2cloud.com",
+ roles: ['admin'],
+ language: "zh-CN"
+ },
+ editor: {
+ id: "editor",
+ name: 'Editor',
+ email: "editor@fit2cloud.com",
+ roles: ['editor'],
+ language: "zh-CN"
+ },
+ readonly: {
+ id: "readonly",
+ name: 'Readonly User',
+ email: "readonly@fit2cloud.com",
+ roles: ['readonly'],
+ language: "zh-CN"
+ }
+}
+
+module.exports = [
+ // user login
+ {
+ url: '/samples/user/login',
+ type: 'post',
+ response: config => {
+ const {username} = config.body
+ const user = users[username];
+
+ // mock error
+ if (!user) {
+ return error("用户名或密码错误")
+ }
+ currentUser = user;
+ return success(user)
+ }
+ },
+
+ {
+ url: '/samples/user/is-login',
+ type: 'get',
+ response: () => {
+ if (currentUser) {
+ return success()
+ } else {
+ return error()
+ }
+ }
+ },
+
+ // get user info
+ {
+ url: '/samples/user/current',
+ type: 'get',
+ response: () => {
+ const info = currentUser
+
+ // mock error
+ if (!info) {
+ return error("用户未登录")
+ }
+
+ return success(info)
+ }
+ },
+
+ // update user info
+ {
+ url: '/samples/user/info/update',
+ type: 'put',
+ response: config => {
+ const {language} = config.body
+ if (currentUser) {
+ currentUser.language = language;
+ }
+ return success(currentUser)
+ }
+ },
+
+ // user logout
+ {
+ url: '/samples/user/logout',
+ type: 'post',
+ response: () => {
+ currentUser = undefined;
+ return success()
+ }
+ }
+]
diff --git a/fit2cloud-view/mock/utils.js b/fit2cloud-view/mock/utils.js
new file mode 100644
index 0000000000..f909a29362
--- /dev/null
+++ b/fit2cloud-view/mock/utils.js
@@ -0,0 +1,48 @@
+/**
+ * @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
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+function deepClone(source) {
+ if (!source && typeof source !== 'object') {
+ throw new Error('error arguments', 'deepClone')
+ }
+ const targetObj = source.constructor === Array ? [] : {}
+ Object.keys(source).forEach(keys => {
+ if (source[keys] && typeof source[keys] === 'object') {
+ targetObj[keys] = deepClone(source[keys])
+ } else {
+ targetObj[keys] = source[keys]
+ }
+ })
+ return targetObj
+}
+
+module.exports = {
+ param2Obj,
+ deepClone
+}
diff --git a/fit2cloud-view/package.json b/fit2cloud-view/package.json
new file mode 100644
index 0000000000..5f74afdb2a
--- /dev/null
+++ b/fit2cloud-view/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "samples",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "lint": "vue-cli-service lint"
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^1.2.34",
+ "@fortawesome/free-brands-svg-icons": "^5.15.2",
+ "@fortawesome/free-regular-svg-icons": "^5.15.2",
+ "@fortawesome/free-solid-svg-icons": "^5.15.2",
+ "@fortawesome/vue-fontawesome": "^2.0.2",
+ "axios": "^0.21.1",
+ "core-js": "^3.6.5",
+ "element-ui": "^2.15.0",
+ "fit2cloud-ui": "^0.1.2",
+ "js-cookie": "^2.2.1",
+ "normalize.css": "^8.0.1",
+ "nprogress": "^0.2.0",
+ "vue": "^2.6.11",
+ "vue-i18n": "^8.22.4",
+ "vuex": "^3.6.0"
+ },
+ "devDependencies": {
+ "@vue/cli-plugin-babel": "~4.5.0",
+ "@vue/cli-plugin-eslint": "~4.5.0",
+ "@vue/cli-service": "~4.5.0",
+ "babel-eslint": "^10.1.0",
+ "eslint": "^6.7.2",
+ "eslint-plugin-vue": "^6.2.2",
+ "mockjs": "^1.1.0",
+ "sass": "^1.32.5",
+ "sass-loader": "^10.1.1",
+ "vue-template-compiler": "^2.6.11"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/essential",
+ "eslint:recommended"
+ ],
+ "parserOptions": {
+ "parser": "babel-eslint"
+ },
+ "rules": {}
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not dead"
+ ]
+}
diff --git a/fit2cloud-view/public/favicon.ico b/fit2cloud-view/public/favicon.ico
new file mode 100644
index 0000000000..df36fcfb72
Binary files /dev/null and b/fit2cloud-view/public/favicon.ico differ
diff --git a/fit2cloud-view/public/index.html b/fit2cloud-view/public/index.html
new file mode 100644
index 0000000000..3e5a139621
--- /dev/null
+++ b/fit2cloud-view/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/App.vue b/fit2cloud-view/src/App.vue
new file mode 100644
index 0000000000..fb5baad926
--- /dev/null
+++ b/fit2cloud-view/src/App.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/api/license.js b/fit2cloud-view/src/api/license.js
new file mode 100644
index 0000000000..e9c2ee19bf
--- /dev/null
+++ b/fit2cloud-view/src/api/license.js
@@ -0,0 +1,7 @@
+import {post} from "@/plugins/request"
+
+export function saveLicense(data) {
+ return post("/samples/license/save", data)
+}
+
+
diff --git a/fit2cloud-view/src/api/user-token.js b/fit2cloud-view/src/api/user-token.js
new file mode 100644
index 0000000000..ba12b7d64b
--- /dev/null
+++ b/fit2cloud-view/src/api/user-token.js
@@ -0,0 +1,21 @@
+/* 前后端分离的登录方式 */
+import {get, post, put} from "@/plugins/request"
+
+export function login(data) {
+ return post("/samples/user-token/login", data)
+}
+
+export function logout() {
+ return post("/samples/user-token/logout")
+}
+
+export function getCurrentUser() {
+ return get("/samples/user-token/current")
+}
+
+export function updateInfo(data) {
+ return put("/samples/user-token/update", data)
+}
+
+
+
diff --git a/fit2cloud-view/src/api/user.js b/fit2cloud-view/src/api/user.js
new file mode 100644
index 0000000000..bdb9daa82e
--- /dev/null
+++ b/fit2cloud-view/src/api/user.js
@@ -0,0 +1,25 @@
+/* 前后端不分离的登录方式 */
+import {get, post, put} from "@/plugins/request"
+
+export function login(data) {
+ return post("/samples/user/login", data)
+}
+
+export function logout() {
+ return post("/samples/user/logout")
+}
+
+export function isLogin() {
+ return get("/samples/user/is-login")
+}
+
+export function getCurrentUser() {
+ return get("/samples/user/current")
+}
+
+export function updateInfo(id, data) {
+ return put("/samples/user/info/update/" + id, data)
+}
+
+
+
diff --git a/fit2cloud-view/src/assets/RackShift-assist-white.png b/fit2cloud-view/src/assets/RackShift-assist-white.png
new file mode 100644
index 0000000000..b2e556678d
Binary files /dev/null and b/fit2cloud-view/src/assets/RackShift-assist-white.png differ
diff --git a/fit2cloud-view/src/assets/RackShift-black.png b/fit2cloud-view/src/assets/RackShift-black.png
new file mode 100644
index 0000000000..4ccf482ea3
Binary files /dev/null and b/fit2cloud-view/src/assets/RackShift-black.png differ
diff --git a/fit2cloud-view/src/assets/RackShift-white.png b/fit2cloud-view/src/assets/RackShift-white.png
new file mode 100644
index 0000000000..ca850ba03c
Binary files /dev/null and b/fit2cloud-view/src/assets/RackShift-white.png differ
diff --git a/fit2cloud-view/src/assets/font/Roboto/Roboto-Regular.ttf b/fit2cloud-view/src/assets/font/Roboto/Roboto-Regular.ttf
new file mode 100755
index 0000000000..2b6392ffe8
Binary files /dev/null and b/fit2cloud-view/src/assets/font/Roboto/Roboto-Regular.ttf differ
diff --git a/fit2cloud-view/src/assets/font/Roboto/index.css b/fit2cloud-view/src/assets/font/Roboto/index.css
new file mode 100644
index 0000000000..b4a21ae2ca
--- /dev/null
+++ b/fit2cloud-view/src/assets/font/Roboto/index.css
@@ -0,0 +1,57 @@
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-display: swap;
+ src: url(Roboto-Regular.ttf) format('truetype');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
diff --git a/fit2cloud-view/src/assets/login-desc.png b/fit2cloud-view/src/assets/login-desc.png
new file mode 100644
index 0000000000..5d7d73fddc
Binary files /dev/null and b/fit2cloud-view/src/assets/login-desc.png differ
diff --git a/fit2cloud-view/src/business/app-layout/header-components/LanguageSwitch.vue b/fit2cloud-view/src/business/app-layout/header-components/LanguageSwitch.vue
new file mode 100644
index 0000000000..895f480758
--- /dev/null
+++ b/fit2cloud-view/src/business/app-layout/header-components/LanguageSwitch.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/app-layout/header-components/PersonalSetting.vue b/fit2cloud-view/src/business/app-layout/header-components/PersonalSetting.vue
new file mode 100644
index 0000000000..baaaeb9be7
--- /dev/null
+++ b/fit2cloud-view/src/business/app-layout/header-components/PersonalSetting.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/app-layout/horizontal-layout/HorizontalHeader.vue b/fit2cloud-view/src/business/app-layout/horizontal-layout/HorizontalHeader.vue
new file mode 100644
index 0000000000..a0285eb501
--- /dev/null
+++ b/fit2cloud-view/src/business/app-layout/horizontal-layout/HorizontalHeader.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/app-layout/horizontal-layout/index.vue b/fit2cloud-view/src/business/app-layout/horizontal-layout/index.vue
new file mode 100644
index 0000000000..0cb8711f16
--- /dev/null
+++ b/fit2cloud-view/src/business/app-layout/horizontal-layout/index.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/dashboard/index.vue b/fit2cloud-view/src/business/dashboard/index.vue
new file mode 100644
index 0000000000..30353888ad
--- /dev/null
+++ b/fit2cloud-view/src/business/dashboard/index.vue
@@ -0,0 +1,15 @@
+
+
+ {{ $t('commons.message_box.prompt') }}
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/directive/ClickOutsideDemo.vue b/fit2cloud-view/src/business/directive/ClickOutsideDemo.vue
new file mode 100644
index 0000000000..d4a3c923d9
--- /dev/null
+++ b/fit2cloud-view/src/business/directive/ClickOutsideDemo.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/directive/PermissionDemo.vue b/fit2cloud-view/src/business/directive/PermissionDemo.vue
new file mode 100644
index 0000000000..91a5af6686
--- /dev/null
+++ b/fit2cloud-view/src/business/directive/PermissionDemo.vue
@@ -0,0 +1,49 @@
+
+
+
切换admin、editor、readonly用户看到不同的内容
+
+
+
+ 需要admin角色才能看到, 指令设置:v-permission="['admin']"
+
+
+
+ 需要editor角色才能看到, 指令设置:v-permission="['editor']"
+
+
+
+ 需要admin或者editor角色才能看到, 指令设置:v-permission="['admin', 'editor']"
+
+
+
+ 任何人都能看到
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/login/index.vue b/fit2cloud-view/src/business/login/index.vue
new file mode 100644
index 0000000000..c05263e7a4
--- /dev/null
+++ b/fit2cloud-view/src/business/login/index.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+

+
+
+ {{ $t('login.title') }}
+
+
+
+ {{ $t('login.welcome') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('commons.button.login') }}
+
+
+
+ {{ msg }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/system-setting/ParamsSetting.vue b/fit2cloud-view/src/business/system-setting/ParamsSetting.vue
new file mode 100644
index 0000000000..0102128649
--- /dev/null
+++ b/fit2cloud-view/src/business/system-setting/ParamsSetting.vue
@@ -0,0 +1,13 @@
+
+ 参数设置
+
+
+
+
+
diff --git a/fit2cloud-view/src/business/system-setting/UserManagement.vue b/fit2cloud-view/src/business/system-setting/UserManagement.vue
new file mode 100644
index 0000000000..76b6d45477
--- /dev/null
+++ b/fit2cloud-view/src/business/system-setting/UserManagement.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/dynamic-table/TablePagination.vue b/fit2cloud-view/src/components/dynamic-table/TablePagination.vue
new file mode 100644
index 0000000000..97b515f7c4
--- /dev/null
+++ b/fit2cloud-view/src/components/dynamic-table/TablePagination.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/dynamic-table/index.vue b/fit2cloud-view/src/components/dynamic-table/index.vue
new file mode 100644
index 0000000000..f0bebdd428
--- /dev/null
+++ b/fit2cloud-view/src/components/dynamic-table/index.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/LayoutContent.vue b/fit2cloud-view/src/components/layout/LayoutContent.vue
new file mode 100644
index 0000000000..223b6457f3
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/LayoutContent.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/LayoutHeader.vue b/fit2cloud-view/src/components/layout/LayoutHeader.vue
new file mode 100644
index 0000000000..a293b00e39
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/LayoutHeader.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/LayoutMain.vue b/fit2cloud-view/src/components/layout/LayoutMain.vue
new file mode 100644
index 0000000000..3daee70f64
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/LayoutMain.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/LayoutSidebar.vue b/fit2cloud-view/src/components/layout/LayoutSidebar.vue
new file mode 100644
index 0000000000..fbe700ebab
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/LayoutSidebar.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/LayoutView.vue b/fit2cloud-view/src/components/layout/LayoutView.vue
new file mode 100644
index 0000000000..f26f429bfc
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/LayoutView.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/index.vue b/fit2cloud-view/src/components/layout/index.vue
new file mode 100644
index 0000000000..7f1dc0f938
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/index.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/sidebar/FixiOSBug.js b/fit2cloud-view/src/components/layout/sidebar/FixiOSBug.js
new file mode 100644
index 0000000000..bc14856f07
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/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/fit2cloud-view/src/components/layout/sidebar/Item.vue b/fit2cloud-view/src/components/layout/sidebar/Item.vue
new file mode 100644
index 0000000000..dd6f843c8b
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/sidebar/Item.vue
@@ -0,0 +1,37 @@
+
+
+
diff --git a/fit2cloud-view/src/components/layout/sidebar/Link.vue b/fit2cloud-view/src/components/layout/sidebar/Link.vue
new file mode 100644
index 0000000000..af0105262c
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/sidebar/Link.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/sidebar/Logo.vue b/fit2cloud-view/src/components/layout/sidebar/Logo.vue
new file mode 100644
index 0000000000..30ff27639a
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/sidebar/Logo.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/sidebar/SidebarItem.vue b/fit2cloud-view/src/components/layout/sidebar/SidebarItem.vue
new file mode 100644
index 0000000000..05048b48bb
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/sidebar/SidebarItem.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/sidebar/SidebarToggleButton.vue b/fit2cloud-view/src/components/layout/sidebar/SidebarToggleButton.vue
new file mode 100644
index 0000000000..bac0fe948f
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/sidebar/SidebarToggleButton.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/layout/sidebar/index.vue b/fit2cloud-view/src/components/layout/sidebar/index.vue
new file mode 100644
index 0000000000..630b8d0bab
--- /dev/null
+++ b/fit2cloud-view/src/components/layout/sidebar/index.vue
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
diff --git a/fit2cloud-view/src/components/redirect/index.vue b/fit2cloud-view/src/components/redirect/index.vue
new file mode 100644
index 0000000000..db4c1d66d1
--- /dev/null
+++ b/fit2cloud-view/src/components/redirect/index.vue
@@ -0,0 +1,12 @@
+
diff --git a/fit2cloud-view/src/directive/click-outside/index.js b/fit2cloud-view/src/directive/click-outside/index.js
new file mode 100644
index 0000000000..7e208289c1
--- /dev/null
+++ b/fit2cloud-view/src/directive/click-outside/index.js
@@ -0,0 +1,8 @@
+import ClickOutside from "element-ui/src/utils/clickoutside";
+
+const install = function (Vue) {
+ Vue.directive("click-outside", ClickOutside)
+}
+
+ClickOutside.install = install
+export default ClickOutside
diff --git a/fit2cloud-view/src/directive/index.js b/fit2cloud-view/src/directive/index.js
new file mode 100644
index 0000000000..53625ad63d
--- /dev/null
+++ b/fit2cloud-view/src/directive/index.js
@@ -0,0 +1,11 @@
+import ClickOutside from "element-ui/src/utils/clickoutside";
+import permission from "@/directive/permission";
+
+export default {
+ install(Vue) {
+ Vue.directive('click-outside', ClickOutside);
+ Vue.directive('permission', permission);
+ }
+}
+
+
diff --git a/fit2cloud-view/src/directive/permission/index.js b/fit2cloud-view/src/directive/permission/index.js
new file mode 100644
index 0000000000..d59c22de7e
--- /dev/null
+++ b/fit2cloud-view/src/directive/permission/index.js
@@ -0,0 +1,29 @@
+import store from '@/store'
+
+function checkPermission(el, binding) {
+ const {value} = binding
+ const roles = store.getters && store.getters.roles
+
+ if (value && value instanceof Array) {
+ if (value.length > 0) {
+ const permissionRoles = value
+
+ const hasPermission = roles.some(role => {
+ return permissionRoles.includes(role)
+ })
+
+ if (!hasPermission) {
+ el.parentNode && el.parentNode.removeChild(el)
+ }
+ }
+ }
+}
+
+export default {
+ inserted(el, binding) {
+ checkPermission(el, binding)
+ },
+ update(el, binding) {
+ checkPermission(el, binding)
+ }
+}
diff --git a/fit2cloud-view/src/i18n/index.js b/fit2cloud-view/src/i18n/index.js
new file mode 100644
index 0000000000..92dd02619f
--- /dev/null
+++ b/fit2cloud-view/src/i18n/index.js
@@ -0,0 +1,75 @@
+import Vue from 'vue';
+import VueI18n from "vue-i18n";
+
+Vue.use(VueI18n);
+
+// 直接加载翻译的语言文件
+const LOADED_LANGUAGES = ['zh-CN', 'en-US'];
+const LANG_FILES = require.context('./lang', true, /\.js$/)
+// 自动加载lang目录下语言文件,默认只加载LOADED_LANGUAGES中规定的语言文件,其他的语言动态加载
+const messages = LANG_FILES.keys().reduce((messages, path) => {
+ const value = LANG_FILES(path)
+ const lang = path.replace(/^\.\/(.*)\.\w+$/, '$1');
+ if (LOADED_LANGUAGES.includes(lang)) {
+ messages[lang] = value.default
+ }
+ return messages;
+}, {})
+
+export const getLanguage = () => {
+ let language = localStorage.getItem('language')
+ if (!language) {
+ language = (navigator.language || navigator.browserLanguage).toLowerCase()
+ }
+ return language;
+}
+
+const i18n = new VueI18n({
+ locale: getLanguage(),
+ messages,
+});
+
+const importLanguage = lang => {
+ if (!LOADED_LANGUAGES.includes(lang)) {
+ return import(`./lang/${lang}`).then(response => {
+ i18n.mergeLocaleMessage(lang, response.default);
+ LOADED_LANGUAGES.push(lang);
+ return Promise.resolve(lang)
+ })
+ }
+ return Promise.resolve(lang)
+}
+
+const setLang = lang => {
+ localStorage.setItem('language', lang)
+ i18n.locale = lang;
+}
+
+export const setLanguage = lang => {
+ if (i18n.locale !== lang) {
+ importLanguage(lang).then(setLang)
+ }
+}
+
+// 组合翻译,例如key为'请输入{0}',keys为login.username,则自动将keys翻译并替换到{0} {1}...
+Vue.prototype.$tm = function (key, ...keys) {
+ let values = [];
+ for (const k of keys) {
+ values.push(i18n.t(k))
+ }
+ return i18n.t(key, values);
+};
+
+// 忽略警告,即:不存在Key直接返回Key
+Vue.prototype.$tk = function (key) {
+ const hasKey = i18n.te(key)
+ if (hasKey) {
+ return i18n.t(key)
+ }
+ return key
+};
+
+// 设置当前语言,LOADED_LANGUAGES以外的翻译文件会自动从lang目录获取(如果有的话), 如果不需要动态加载语言文件,直接用setLang
+Vue.prototype.$setLang = setLanguage;
+
+export default i18n;
diff --git a/fit2cloud-view/src/i18n/lang/en-US.js b/fit2cloud-view/src/i18n/lang/en-US.js
new file mode 100644
index 0000000000..dbb535b258
--- /dev/null
+++ b/fit2cloud-view/src/i18n/lang/en-US.js
@@ -0,0 +1,13 @@
+import el from "element-ui/lib/locale/lang/en";
+import fu from "fit2cloud-ui/src/locale/lang/en_US"; // 加载fit2cloud的内容
+
+const message = {
+ // TODO
+}
+
+export default {
+ ...el,
+ ...fu,
+ ...message
+};
+
diff --git a/fit2cloud-view/src/i18n/lang/zh-CN.js b/fit2cloud-view/src/i18n/lang/zh-CN.js
new file mode 100644
index 0000000000..037b34a16b
--- /dev/null
+++ b/fit2cloud-view/src/i18n/lang/zh-CN.js
@@ -0,0 +1,54 @@
+import el from "element-ui/lib/locale/lang/zh-CN"; // 加载element的内容
+import fu from "fit2cloud-ui/src/locale/lang/zh-CN"; // 加载fit2cloud的内容
+
+const message = {
+ commons: {
+ message_box: {
+ alert: "警告",
+ confirm: "确认",
+ prompt: "提示",
+ },
+ button: {
+ login: "登录",
+ ok: "确定",
+ save: "保存",
+ delete: "删除",
+ cancel: "取消",
+ return: "返回",
+ },
+ msg: {
+ success: "{0}成功",
+ op_success: "操作成功",
+ save_success: "保存成功",
+ delete_success: "删除成功",
+ },
+ validate: {
+ limit: '长度在 {0} 到 {1} 个字符',
+ input: "请输入{0}",
+ select: "请选择{0}",
+ },
+ personal: {
+ personal_information: "个人信息",
+ help_documentation: "帮助文档",
+ exit_system: "退出系统",
+ }
+ },
+ login: {
+ username: "用户名",
+ password: "密码",
+ title: "登录 FIT2CLOUD",
+ welcome: "欢迎回来,请输入用户名和密码登录",
+ expires: '认证信息已过期,请重新登录',
+ },
+ route: {
+ system_setting: "系统设置",
+ user_management: "用户管理",
+ params_setting: "参数设置",
+ },
+}
+
+export default {
+ ...el,
+ ...fu,
+ ...message
+};
diff --git a/fit2cloud-view/src/i18n/lang/zh-TW.js b/fit2cloud-view/src/i18n/lang/zh-TW.js
new file mode 100644
index 0000000000..911e5c64bc
--- /dev/null
+++ b/fit2cloud-view/src/i18n/lang/zh-TW.js
@@ -0,0 +1,10 @@
+import el from "element-ui/lib/locale/lang/zh-TW";
+
+const message = {
+ // TODO
+}
+
+export default {
+ ...el,
+ ...message
+};
diff --git a/fit2cloud-view/src/icons/index.js b/fit2cloud-view/src/icons/index.js
new file mode 100644
index 0000000000..f9a8ec2a9b
--- /dev/null
+++ b/fit2cloud-view/src/icons/index.js
@@ -0,0 +1,12 @@
+import {library} from '@fortawesome/fontawesome-svg-core'
+import {fas} from '@fortawesome/free-solid-svg-icons'
+import {far} from '@fortawesome/free-regular-svg-icons'
+import {fab} from '@fortawesome/free-brands-svg-icons'
+import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
+
+export default {
+ install(Vue) {
+ library.add(fas, far, fab);
+ Vue.component('font-awesome-icon', FontAwesomeIcon);
+ }
+}
diff --git a/fit2cloud-view/src/main.js b/fit2cloud-view/src/main.js
new file mode 100644
index 0000000000..5253c0549e
--- /dev/null
+++ b/fit2cloud-view/src/main.js
@@ -0,0 +1,33 @@
+import Vue from 'vue'
+import "@/styles/index.scss"
+import Fit2CloudUI from 'fit2cloud-ui';
+import ElementUI from 'element-ui';
+import App from './App.vue'
+import i18n from "./i18n";
+import router from './router'
+import store from './store'
+import icons from './icons'
+import plugins from "./plugins";
+import directives from "./directive";
+import "./permission"
+
+Vue.config.productionTip = false
+
+Vue.use(ElementUI, {
+ size: 'small',
+ i18n: (key, value) => i18n.t(key, value)
+});
+Vue.use(Fit2CloudUI, {
+ i18n: (key, value) => i18n.t(key, value)
+});
+Vue.use(icons);
+Vue.use(plugins);
+Vue.use(directives);
+
+new Vue({
+ el: '#app',
+ i18n,
+ router,
+ store,
+ render: h => h(App),
+})
diff --git a/fit2cloud-view/src/permission.js b/fit2cloud-view/src/permission.js
new file mode 100644
index 0000000000..d431c23090
--- /dev/null
+++ b/fit2cloud-view/src/permission.js
@@ -0,0 +1,57 @@
+import router from './router'
+import store from './store'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+
+NProgress.configure({showSpinner: false}) // NProgress Configuration
+
+const whiteList = ['/login'] // no redirect whitelist
+
+const generateRoutes = async (to, from, next) => {
+ const hasRoles = store.getters.roles && store.getters.roles.length > 0
+ if (hasRoles) {
+ next()
+ } else {
+ try {
+ const {roles} = await store.dispatch('user/getCurrentUser')
+ const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
+ router.addRoutes(accessRoutes)
+ next({...to, replace: true})
+ } catch (error) {
+ await store.dispatch('user/logout')
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
+ }
+ }
+}
+
+// 路由前置钩子,根据实际需求修改
+router.beforeEach(async (to, from, next) => {
+ NProgress.start()
+
+ const isLogin = await store.dispatch('user/isLogin') // 或者user-token/isLogin
+
+ if (isLogin) {
+ if (to.path === '/login') {
+ next({path: '/'})
+ NProgress.done()
+ } else {
+ await generateRoutes(to, from, next)
+ }
+ } else {
+ /* has not login*/
+ 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/fit2cloud-view/src/plugins/index.js b/fit2cloud-view/src/plugins/index.js
new file mode 100644
index 0000000000..a99e948028
--- /dev/null
+++ b/fit2cloud-view/src/plugins/index.js
@@ -0,0 +1,9 @@
+import message from "@/plugins/message";
+import request from "@/plugins/request";
+
+export default {
+ install(Vue) {
+ Vue.use(message);
+ Vue.use(request);
+ }
+}
diff --git a/fit2cloud-view/src/plugins/message.js b/fit2cloud-view/src/plugins/message.js
new file mode 100644
index 0000000000..425a40dacd
--- /dev/null
+++ b/fit2cloud-view/src/plugins/message.js
@@ -0,0 +1,71 @@
+import {MessageBox, Message} from 'element-ui';
+import i18n from "@/i18n";
+
+export const $alert = (message, callback, options) => {
+ let title = i18n.t("common.message_box.alert");
+ MessageBox.alert(message, title, options).then(() => {
+ callback();
+ });
+}
+
+export const $confirm = (message, callback, options = {}) => {
+ let defaultOptions = {
+ confirmButtonText: i18n.t("common.button.ok"),
+ cancelButtonText: i18n.t("common.button.cancel"),
+ type: 'warning',
+ ...options
+ }
+ let title = i18n.t("common.message_box.confirm");
+ MessageBox.confirm(message, title, defaultOptions).then(() => {
+ callback();
+ });
+}
+
+export const $success = (message, duration) => {
+ Message.success({
+ message: message,
+ type: "success",
+ showClose: true,
+ duration: duration || 1500
+ })
+}
+
+export const $info = (message, duration) => {
+ Message.info({
+ message: message,
+ type: "info",
+ showClose: true,
+ duration: duration || 3000
+ })
+}
+
+export const $warning = (message, duration) => {
+ Message.warning({
+ message: message,
+ type: "warning",
+ showClose: true,
+ duration: duration || 5000
+ })
+}
+
+export const $error = (message, duration) => {
+ Message.error({
+ message: message,
+ type: "error",
+ showClose: true,
+ duration: duration || 10000
+ })
+}
+
+export default {
+ install(Vue) {
+ // 使用$$前缀,避免与Element UI的冲突
+ Vue.prototype.$$confirm = $confirm;
+ Vue.prototype.$$alert = $alert;
+
+ Vue.prototype.$success = $success;
+ Vue.prototype.$info = $info;
+ Vue.prototype.$warning = $warning;
+ Vue.prototype.$error = $error;
+ }
+}
diff --git a/fit2cloud-view/src/plugins/request.js b/fit2cloud-view/src/plugins/request.js
new file mode 100644
index 0000000000..5ce85c91c0
--- /dev/null
+++ b/fit2cloud-view/src/plugins/request.js
@@ -0,0 +1,108 @@
+import axios from 'axios'
+import {$alert, $error} from "./message"
+import store from '@/store'
+import i18n from "@/i18n";
+import {TokenKey, getToken} from '@/utils/token'
+
+const instance = axios.create({
+ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+ withCredentials: true,
+ timeout: 60000 // request timeout, default 1 min
+})
+
+// 每次请求加上Token。如果没用使用Token,删除这个拦截器
+instance.interceptors.request.use(
+ config => {
+ if (store.getters.token) {
+ config.headers[TokenKey] = getToken()
+ }
+ return config
+ },
+ error => {
+ console.log(error) // for debug
+ return Promise.reject(error)
+ }
+)
+
+const checkAuth = response => {
+ // 请根据实际需求修改
+ if (response.headers["authentication-status"] === "invalid" || response.status === 401) {
+ let message = i18n.t('login.expires');
+ $alert(message, () => {
+ store.dispatch('user/logout').then(() => {
+ location.reload()
+ })
+ });
+ }
+}
+
+const checkPermission = response => {
+ // 请根据实际需求修改
+ if (response.status === 403) {
+ location.href = "/403";
+ }
+}
+
+// 请根据实际需求修改
+instance.interceptors.response.use(response => {
+ checkAuth(response);
+ return response;
+}, error => {
+ let msg;
+ if (error.response) {
+ checkAuth(error.response);
+ checkPermission(error.response);
+ msg = error.response.data.message || error.response.data;
+ } else {
+ console.log('error: ' + error) // for debug
+ msg = error.message;
+ }
+ $error(msg)
+ return Promise.reject(error);
+});
+
+export const request = instance
+
+/* 简化请求方法,统一处理返回结果,并增加loading处理,这里以{success,data,message}格式的返回值为例,具体项目根据实际需求修改 */
+const promise = (request, loading = {}) => {
+ return new Promise((resolve, reject) => {
+ loading.status = true;
+ request.then(response => {
+ if (response.data.success) {
+ resolve(response.data);
+ } else {
+ reject(response.data)
+ }
+ loading.status = false;
+ }).catch(error => {
+ reject(error)
+ loading.status = false;
+ })
+ })
+}
+
+export const get = (url, data, loading) => {
+ return promise(request({url: url, method: "get", params: data}), loading)
+};
+
+export const post = (url, data, loading) => {
+ return promise(request({url: url, method: "post", data}), loading)
+};
+
+export const put = (url, data, loading) => {
+ return promise(request({url: url, method: "put", data}), loading)
+};
+
+export const del = (url, loading) => {
+ return promise(request({url: url, method: "delete"}), loading)
+};
+
+export default {
+ install(Vue) {
+ Vue.prototype.$get = get;
+ Vue.prototype.$post = post;
+ Vue.prototype.$put = put;
+ Vue.prototype.$delete = del;
+ Vue.prototype.$request = request;
+ }
+}
diff --git a/fit2cloud-view/src/router/index.js b/fit2cloud-view/src/router/index.js
new file mode 100644
index 0000000000..7178a65383
--- /dev/null
+++ b/fit2cloud-view/src/router/index.js
@@ -0,0 +1,69 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+// 加载modules中的路由
+const modules = require.context('./modules', true, /\.js$/)
+
+// 修复路由变更后报错的问题
+const routerPush = Router.prototype.push;
+Router.prototype.push = function push(location) {
+ return routerPush.call(this, location).catch(error => error)
+}
+
+Vue.use(Router)
+
+import Layout from '@/business/app-layout/horizontal-layout'
+
+export const constantRoutes = [
+ {
+ path: '/redirect',
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: '/redirect/:path(.*)',
+ component: () => import('@/components/redirect')
+ }
+ ]
+ },
+ {
+ path: '/login',
+ component: () => import('@/business/login'),
+ hidden: true
+ },
+ {
+ path: '/',
+ component: Layout,
+ redirect: '/dashboard',
+ children: [
+ {
+ path: 'dashboard',
+ component: () => import('@/business/dashboard'),
+ name: 'Dashboard',
+ meta: {title: 'Dashboard', icon: 'el-icon-s-marketing', affix: true}
+ }
+ ]
+ }
+]
+
+/**
+ * 用户登录后根据角色加载的路由
+ */
+export const rolesRoutes = [
+ ...modules.keys().map(key => modules(key).default),
+ {path: '*', redirect: '/', hidden: true}
+]
+
+const createRouter = () => new Router({
+ scrollBehavior: () => ({y: 0}),
+ routes: constantRoutes
+})
+
+const router = createRouter()
+
+export function resetRouter() {
+ const newRouter = createRouter()
+ router.matcher = newRouter.matcher // reset router
+}
+
+export default router
diff --git a/fit2cloud-view/src/router/modules/directives.js b/fit2cloud-view/src/router/modules/directives.js
new file mode 100644
index 0000000000..f75da30f9e
--- /dev/null
+++ b/fit2cloud-view/src/router/modules/directives.js
@@ -0,0 +1,30 @@
+import Layout from "@/business/app-layout/horizontal-layout";
+
+const Directive = {
+ path: '/directive',
+ component: Layout,
+ name: 'Directive',
+ meta: {
+ title: "指令示例",
+ icon: 'el-icon-setting',
+ },
+ children: [
+ {
+ path: 'click-outside',
+ component: () => import('@/business/directive/ClickOutsideDemo'),
+ name: "ClickOutside",
+ meta: {
+ title: "点击外部指令"
+ }
+ },
+ {
+ path: 'permission',
+ component: () => import('@/business/directive/PermissionDemo'),
+ name: "Permission",
+ meta: {
+ title: "权限指令"
+ }
+ }
+ ]
+}
+export default Directive
diff --git a/fit2cloud-view/src/router/modules/filters.js b/fit2cloud-view/src/router/modules/filters.js
new file mode 100644
index 0000000000..405c1a3ff8
--- /dev/null
+++ b/fit2cloud-view/src/router/modules/filters.js
@@ -0,0 +1,12 @@
+import Layout from "@/business/app-layout/horizontal-layout";
+
+const Filters = {
+ path: '/filters',
+ component: Layout,
+ name: 'Filters',
+ meta: {
+ title: "过滤器示例",
+ icon: 'el-icon-setting',
+ }
+}
+export default Filters
diff --git a/fit2cloud-view/src/router/modules/system-setting.js b/fit2cloud-view/src/router/modules/system-setting.js
new file mode 100644
index 0000000000..2a895acfed
--- /dev/null
+++ b/fit2cloud-view/src/router/modules/system-setting.js
@@ -0,0 +1,33 @@
+import Layout from "@/business/app-layout/horizontal-layout";
+
+const SystemSetting = {
+ path: '/system-setting',
+ component: Layout,
+ name: 'SystemSetting',
+ meta: {
+ title: "route.system_setting",
+ icon: 'el-icon-setting',
+ roles: ['admin']
+ },
+ children: [
+ {
+ path: 'user-management',
+ component: () => import('@/business/system-setting/UserManagement'),
+ name: "UserManagement",
+ meta: {
+ title: "route.user_management",
+ roles: ['admin']
+ }
+ },
+ {
+ path: 'params-setting',
+ component: () => import('@/business/system-setting/ParamsSetting'),
+ name: "ParamsSetting",
+ meta: {
+ title: "route.params_setting",
+ roles: ['admin']
+ }
+ }
+ ]
+}
+export default SystemSetting
diff --git a/fit2cloud-view/src/store/getters.js b/fit2cloud-view/src/store/getters.js
new file mode 100644
index 0000000000..a589dc74b0
--- /dev/null
+++ b/fit2cloud-view/src/store/getters.js
@@ -0,0 +1,10 @@
+// 根据实际需要修改
+const getters = {
+ sidebar: state => state.app.sidebar,
+ name: state => state.user.name,
+ language: state => state.user.language,
+ roles: state => state.user.roles,
+ permission_routes: state => state.permission.routes,
+ license: state => state.license,
+}
+export default getters
diff --git a/fit2cloud-view/src/store/index.js b/fit2cloud-view/src/store/index.js
new file mode 100644
index 0000000000..99e44bff33
--- /dev/null
+++ b/fit2cloud-view/src/store/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+
+Vue.use(Vuex)
+
+// 自动从modules目录下获取模块
+const MODULES_FILES = require.context('./modules', true, /\.js$/)
+
+// 模块名为js文件名,例如user.js 则模块名为user
+const modules = MODULES_FILES.keys().reduce((modules, modulePath) => {
+ const value = MODULES_FILES(modulePath)
+ const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
+ modules[moduleName] = value.default
+ return modules
+}, {})
+
+const store = new Vuex.Store({
+ modules,
+ getters
+})
+
+export default store
diff --git a/fit2cloud-view/src/store/modules/app.js b/fit2cloud-view/src/store/modules/app.js
new file mode 100644
index 0000000000..9068d3729b
--- /dev/null
+++ b/fit2cloud-view/src/store/modules/app.js
@@ -0,0 +1,50 @@
+const get = () => {
+ return localStorage.getItem('sidebarStatus')
+}
+const set = value => {
+ localStorage.setItem('sidebarStatus', value)
+}
+const state = {
+ sidebar: {
+ opened: get() ? !!+get() : true
+ },
+ device: 'desktop'
+}
+
+const mutations = {
+ TOGGLE_SIDEBAR: state => {
+ state.sidebar.opened = !state.sidebar.opened
+ if (state.sidebar.opened) {
+ set(1)
+ } else {
+ set(0)
+ }
+ },
+ OPEN_SIDEBAR: (state) => {
+ set('sidebarStatus', 1)
+ state.sidebar.opened = true
+ },
+ CLOSE_SIDEBAR: (state) => {
+ set('sidebarStatus', 0)
+ state.sidebar.opened = false
+ }
+}
+
+const actions = {
+ toggleSideBar({commit}) {
+ commit('TOGGLE_SIDEBAR')
+ },
+ openSideBar({commit}) {
+ commit('OPEN_SIDEBAR')
+ },
+ closeSideBar({commit}) {
+ commit('CLOSE_SIDEBAR')
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/fit2cloud-view/src/store/modules/license.js b/fit2cloud-view/src/store/modules/license.js
new file mode 100644
index 0000000000..dddc77f6b0
--- /dev/null
+++ b/fit2cloud-view/src/store/modules/license.js
@@ -0,0 +1,64 @@
+import {saveLicense} from "@/api/license"
+
+const LicenseKey = "X-License";
+
+const Status = {
+ valid: "valid",
+ invalid: "invalid",
+ expired: "expired",
+}
+
+const get = () => {
+ return localStorage.getItem(LicenseKey)
+}
+const set = value => {
+ localStorage.setItem(LicenseKey, value)
+}
+const state = {
+ status: get(),
+ license: {},
+ message: ""
+}
+
+const mutations = {
+ SET_STATUS: (state, status) => {
+ set(LicenseKey, status)
+ state.status = status;
+ },
+ SET_LICENSE: (state, license) => {
+ state.license = license;
+ },
+ SET_MESSAGE: (state, message) => {
+ state.message = message;
+ }
+}
+
+const actions = {
+ saveLicense({commit}, content) {
+ return new Promise((resolve, reject) => {
+ saveLicense({license: content}).then(response => {
+ const {status, license, message} = response.data;
+ commit('SET_STATUS', status)
+ commit('SET_LICENSE', license)
+ commit('SET_MESSAGE', message)
+ resolve(status)
+ }).catch(error => {
+ commit('SET_STATUS', Status.invalid)
+ reject(error)
+ })
+ })
+ },
+ isValid({state}) {
+ return state.status === Status.valid
+ },
+ isExpired({state}) {
+ return state.status === Status.expired
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/fit2cloud-view/src/store/modules/permission.js b/fit2cloud-view/src/store/modules/permission.js
new file mode 100644
index 0000000000..57505565eb
--- /dev/null
+++ b/fit2cloud-view/src/store/modules/permission.js
@@ -0,0 +1,61 @@
+import {rolesRoutes, constantRoutes} from '@/router'
+
+function hasPermission(roles, route) {
+ if (route.meta && route.meta.roles) {
+ return roles.some(role => route.meta.roles.includes(role))
+ } else {
+ return true
+ }
+}
+
+export function filterRolesRoutes(routes, roles) {
+ const res = []
+
+ routes.forEach(route => {
+ const tmp = {...route}
+ if (hasPermission(roles, tmp)) {
+ if (tmp.children) {
+ tmp.children = filterRolesRoutes(tmp.children, roles)
+ }
+ res.push(tmp)
+ }
+ })
+
+ return res
+}
+
+const state = {
+ routes: [],
+ addRoutes: []
+}
+
+const mutations = {
+ SET_ROUTES: (state, routes) => {
+ state.addRoutes = routes
+ state.routes = constantRoutes.concat(routes)
+ }
+}
+
+const actions = {
+ generateRoutes({commit}, roles) {
+ return new Promise(resolve => {
+ let accessedRoutes
+ if (roles.includes('admin')) {
+ // admin角色加载所有路由
+ accessedRoutes = rolesRoutes || []
+ } else {
+ // 其他角色加载对应角色的路由
+ accessedRoutes = filterRolesRoutes(rolesRoutes, roles)
+ }
+ commit('SET_ROUTES', accessedRoutes)
+ resolve(accessedRoutes)
+ })
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/fit2cloud-view/src/store/modules/user-token.js b/fit2cloud-view/src/store/modules/user-token.js
new file mode 100644
index 0000000000..78735dded6
--- /dev/null
+++ b/fit2cloud-view/src/store/modules/user-token.js
@@ -0,0 +1,97 @@
+import {login, getCurrentUser, updateInfo, logout} from '@/api/user-token'
+import {resetRouter} from '@/router'
+import {getToken, setToken, removeToken} from '@/utils/token'
+import {getLanguage, setLanguage} from "@/i18n";
+
+/* 前后端不分离的登录办法*/
+const state = {
+ token: getToken(),
+ name: "",
+ language: getLanguage(),
+ roles: []
+}
+
+const mutations = {
+ SET_TOKEN: (state, token) => {
+ state.token = token
+ },
+ SET_NAME: (state, name) => {
+ state.name = name
+ },
+ SET_LANGUAGE: (state, language) => {
+ state.language = language
+ setLanguage(language)
+ },
+ SET_ROLES: (state, roles) => {
+ state.roles = roles
+ }
+}
+
+const actions = {
+ login({commit}, userInfo) {
+ const {username, password} = userInfo
+ return new Promise((resolve, reject) => {
+ login({username: username.trim(), password: password}).then(response => {
+ let token = response.data
+ commit('SET_TOKEN', token)
+ setToken(token)
+ resolve(response)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ isLogin({commit}) {
+ return new Promise((resolve, reject) => {
+ let token = getToken()
+ if (token) {
+ commit('SET_TOKEN', token);
+ resolve(true)
+ } else {
+ reject(false)
+ }
+ });
+ },
+
+ getCurrentUser({commit}) {
+ return new Promise((resolve, reject) => {
+ getCurrentUser().then(response => {
+ const {name, roles, language} = response.data
+ commit('SET_NAME', name)
+ commit('SET_ROLES', roles)
+ commit('SET_LANGUAGE', language)
+ resolve(response.data)
+ }).catch(error => {
+ reject(error)
+ })
+ });
+ },
+
+ setLanguage({commit, state}, language) {
+ commit('SET_LANGUAGE', language)
+ return new Promise((resolve, reject) => {
+ updateInfo(state.id, {language: language}).then(response => {
+ resolve(response)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ logout({commit}) {
+ logout().then(() => {
+ commit('SET_TOKEN', "");
+ commit('SET_ROLES', [])
+ removeToken()
+ resetRouter()
+ })
+ },
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/fit2cloud-view/src/store/modules/user.js b/fit2cloud-view/src/store/modules/user.js
new file mode 100644
index 0000000000..6b90773972
--- /dev/null
+++ b/fit2cloud-view/src/store/modules/user.js
@@ -0,0 +1,99 @@
+/* 前后端不分离的登录方式*/
+import {login, isLogin, getCurrentUser, updateInfo, logout} from '@/api/user'
+import {resetRouter} from '@/router'
+import {getLanguage, setLanguage} from "@/i18n";
+
+const state = {
+ login: false,
+ name: "",
+ language: getLanguage(),
+ roles: []
+}
+
+const mutations = {
+ LOGIN: (state) => {
+ state.login = true
+ },
+ LOGOUT: (state) => {
+ state.login = false
+ },
+ SET_NAME: (state, name) => {
+ state.name = name
+ },
+ SET_LANGUAGE: (state, language) => {
+ state.language = language
+ setLanguage(language)
+ },
+ SET_ROLES: (state, roles) => {
+ state.roles = roles
+ }
+}
+
+const actions = {
+ login({commit}, userInfo) {
+ const {username, password} = userInfo
+ return new Promise((resolve, reject) => {
+ login({username: username.trim(), password: password}).then(response => {
+ commit('LOGIN')
+ resolve(response)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ isLogin({commit}) {
+ return new Promise((resolve) => {
+ if (state.login) {
+ resolve(true)
+ return;
+ }
+ isLogin().then(() => {
+ commit('LOGIN')
+ resolve(true)
+ }).catch(() => {
+ resolve(false)
+ })
+ });
+ },
+
+ getCurrentUser({commit}) {
+ return new Promise((resolve, reject) => {
+ getCurrentUser().then(response => {
+ const {name, roles, language} = response.data
+ commit('SET_NAME', name)
+ commit('SET_ROLES', roles)
+ commit('SET_LANGUAGE', language)
+ resolve(response.data)
+ }).catch(error => {
+ reject(error)
+ })
+ });
+ },
+
+ setLanguage({commit, state}, language) {
+ commit('SET_LANGUAGE', language)
+ return new Promise((resolve, reject) => {
+ updateInfo(state.id, {language: language}).then(response => {
+ resolve(response)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ logout({commit}) {
+ logout().then(() => {
+ commit('LOGOUT')
+ commit('SET_ROLES', [])
+ resetRouter()
+ })
+ },
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/fit2cloud-view/src/styles/business/app.scss b/fit2cloud-view/src/styles/business/app.scss
new file mode 100644
index 0000000000..8b02be0c82
--- /dev/null
+++ b/fit2cloud-view/src/styles/business/app.scss
@@ -0,0 +1,54 @@
+@import "~@/assets/font/Roboto/index.css";
+
+html {
+ height: 100%;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: Roboto, Helvetica, PingFang SC, Arial, sans-serif;
+ font-size: 14px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+ height: 100%;
+}
+
+#app {
+ height: 100%;
+}
+
+:focus {
+ outline: none;
+}
+
+a:active {
+ outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+ cursor: pointer;
+ color: inherit;
+ text-decoration: none;
+}
+
+// 滚动条整体部分
+::-webkit-scrollbar {
+ width: 6px; // 纵向滚动条宽度
+ height: 6px; // 横向滚动条高度
+}
+
+// 滑块
+::-webkit-scrollbar-thumb {
+ border-radius: 5px;
+ background-color: #4A4B4D;
+}
+
+// 轨道
+::-webkit-scrollbar-track {
+ border-radius: 5px;
+ background-color: transparent;
+}
+
diff --git a/fit2cloud-view/src/styles/business/header-menu.scss b/fit2cloud-view/src/styles/business/header-menu.scss
new file mode 100644
index 0000000000..d25e4a8fac
--- /dev/null
+++ b/fit2cloud-view/src/styles/business/header-menu.scss
@@ -0,0 +1,39 @@
+@import "~@/styles/common/variables.scss";
+
+.header-menu {
+ min-width: 150px;
+ color: #3E3E3D;
+
+ &.el-menu {
+ background-color: transparent;
+
+ &.el-menu--horizontal {
+ border: none;
+
+ .el-submenu__title {
+ border: none;
+ min-width: 150px;
+ height: 40px;
+ line-height: 40px;
+ }
+ }
+ }
+}
+
+.header-menu-popper {
+ color: #3E3E3D;
+
+ .el-menu--popup {
+ min-width: 150px;
+ }
+
+ .el-menu-item {
+ &.is-active {
+ color: $--color-primary;
+ }
+
+ &:hover {
+ background-color: #D5D5D5;
+ }
+ }
+}
diff --git a/fit2cloud-view/src/styles/common/mixins.scss b/fit2cloud-view/src/styles/common/mixins.scss
new file mode 100644
index 0000000000..e365c9bd39
--- /dev/null
+++ b/fit2cloud-view/src/styles/common/mixins.scss
@@ -0,0 +1,15 @@
+@mixin flex-row($justify: flex-start, $align: stretch) {
+ display: flex;
+ @if $justify != flex-start {
+ justify-content: $justify;
+ }
+ @if $align != stretch {
+ align-items: $align;
+ }
+}
+
+@mixin click-active-scale($scale:0.95) {
+ &:active {
+ transform: scale($scale);
+ }
+}
diff --git a/fit2cloud-view/src/styles/common/variables.scss b/fit2cloud-view/src/styles/common/variables.scss
new file mode 100644
index 0000000000..3212a7de6d
--- /dev/null
+++ b/fit2cloud-view/src/styles/common/variables.scss
@@ -0,0 +1,48 @@
+/* Element 变量 */
+$--color-primary: #447DF7;
+$--color-success: #87CB16;
+$--color-warning: #FFA534;
+$--color-danger: #FB404B;
+
+$--box-shadow-light: 0 1px 4px 0 rgb(0 0 0 / 14%);
+
+$--color-text-primary: #3c4858;
+
+/* layout */
+$layout-bg-color: #F2F2F2;
+
+/* sidebar */
+$sidebar-open-width: 260px;
+$sidebar-close-width: 80px;
+$sidebar-bg-color: #30373d;
+$sidebar-bg-gradient: linear-gradient(to bottom right, #30373D, #3E3E3D);
+
+/* menu */
+$menu-color: #BFCBD9;
+$menu-active-color: #FFF;
+$menu-active-bg-color: rgb(200 200 200 / 20%);
+$menu-bg-color: transparent;
+$menu-bg-color-hover: #4A4B4D;
+$menu-height: 50px;
+$submenu-height: 40px;
+$submenu-active-color: #FFF;
+$submenu-active-bg-color: $menu-active-bg-color;
+
+/* logo */
+$logo-height: 40px;
+$logo-bg-color: #4E5051;
+
+/* header */
+$header-height: 60px;
+$header-padding: 30px;
+
+/* main */
+$view-padding: 15px;
+
+/* fit2cloud-ui的variables加载了element-ui的变量 */
+@import "~fit2cloud-ui/src/styles/common/variables";
+
+:export {
+ theme: $--color-primary;
+}
+
diff --git a/fit2cloud-view/src/styles/index.scss b/fit2cloud-view/src/styles/index.scss
new file mode 100644
index 0000000000..f0d62039a0
--- /dev/null
+++ b/fit2cloud-view/src/styles/index.scss
@@ -0,0 +1,4 @@
+@import '~normalize.css/normalize.css';
+@import "./common/variables";
+@import "~fit2cloud-ui/src/styles";
+@import "./business/app";
diff --git a/fit2cloud-view/src/utils/token.js b/fit2cloud-view/src/utils/token.js
new file mode 100644
index 0000000000..21195b9df3
--- /dev/null
+++ b/fit2cloud-view/src/utils/token.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+export const TokenKey = 'App-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/fit2cloud-view/src/utils/validate.js b/fit2cloud-view/src/utils/validate.js
new file mode 100644
index 0000000000..7b5dfcb9f6
--- /dev/null
+++ b/fit2cloud-view/src/utils/validate.js
@@ -0,0 +1,3 @@
+export function isExternal(path) {
+ return /^(https?:|mailto:|tel:)/.test(path)
+}
diff --git a/fit2cloud-view/vue.config.js b/fit2cloud-view/vue.config.js
new file mode 100644
index 0000000000..e77f332714
--- /dev/null
+++ b/fit2cloud-view/vue.config.js
@@ -0,0 +1,37 @@
+const path = require('path')
+
+function resolve(dir) {
+ return path.join(__dirname, dir)
+}
+
+module.exports = {
+ productionSourceMap: true,
+ // 使用mock-server
+ devServer: {
+ port: 8080,
+ open: true,
+ overlay: {
+ warnings: false,
+ errors: true
+ },
+ before: require('./mock/mock-server.js')
+ },
+ // 不使用mock-server,直接连接开发服务器
+ // devServer: {
+ // port: 8080,
+ // proxy: {
+ // ['^(?!/login)']: {
+ // target: 'http://localhost:8081',
+ // ws: true,
+ // }
+ // }
+ // },
+ configureWebpack: {
+ devtool: 'source-map',
+ resolve: {
+ alias: {
+ '@': resolve('src')
+ }
+ }
+ }
+};
diff --git a/fit2cloud-view/代码规范.MD b/fit2cloud-view/代码规范.MD
new file mode 100644
index 0000000000..536d7695af
--- /dev/null
+++ b/fit2cloud-view/代码规范.MD
@@ -0,0 +1,23 @@
+####文件命名:
+- html 小写字母+横线,例如:index.html,org-list.html
+- js 小写字母+横线,例如:i18n.js,en-US.js
+- vue 驼峰命名,首字母大写,例如Login.vue,HeaderUser.vue
+
+####变量命名:
+- 常量 大写字母加下划线,例如:const ROLE_ADMIN='admin'
+- 变量 驼峰命名,首字母小写,例如let name,let currentProject
+- 方法 驼峰命名,首字母小写,例如function open(){},function openDialog()
+
+####Vue组件:
+- 导出名称 驼峰命名,首字母大写,以Ms开头,例如MsUser
+
+####样式规范:
+- 控件的样式写在vue文件的中
+- 公共样式(多个控件使用)写在单独的scss文件中
+- 命名 小写字母+横线,例如.menu,.header-menu,#header-top
+
+####格式要求:
+- 遵循.editorconfig
+
+####Vue风格指南:
+- https://cn.vuejs.org/v2/style-guide/
diff --git a/fit2cloud-view/国际化规范.md b/fit2cloud-view/国际化规范.md
new file mode 100644
index 0000000000..385769b4e1
--- /dev/null
+++ b/fit2cloud-view/国际化规范.md
@@ -0,0 +1,74 @@
+# 国际化文件书写规范
+
+### 文件内容
+
+每个语言文件由element-ui的国际化内容和自定义国际化内容组成,以zh_CN.js为例:
+
+```js
+import el from "element-ui/lib/locale/lang/zh-CN";
+
+const message = {
+ ...
+}
+
+export default {
+ ...el, // element-ui的国际化内容
+ ...message // 自定义内容
+};
+```
+
+### 自定义内容
+
+自定义部分按照业务模块划分,通用的写在commons内,例如
+
+```js
+const message = {
+ commons: { // 通用
+ ...
+ },
+ login: { // 登录
+ ...
+ },
+ ... // 其他模块
+}
+
+```
+
+### 层级结构
+
+按照业务模块划分后,仍然可以按照子业务或功能再进行划分,但每个业务模块下不要超过3层,例如:
+
+```js
+const message = {
+ user_manager: {
+ user_list: { // 用户列表
+ name: "姓名",
+ search: {
+ ... // 用户列表查询
+ },
+ ... // 用户列表
+ },
+ user_edit: {
+ ... // 编辑用户
+ }
+ },
+ ... // 其他模块
+}
+
+```
+
+### Key命名
+
+所有Key的命名必须采用英文单词的方式命名,多个单词之间用下划线( _ )连接,尽量让人一看就知道这个key代表的意思, 例如:user_list
+
+```js
+const message = {
+ user_manager: {
+ user_list: {
+ ...
+ },
+ user_edit: {}
+ },
+}
+
+```
diff --git a/fit2cloud-view/目录结构.md b/fit2cloud-view/目录结构.md
new file mode 100644
index 0000000000..fa0cad2d04
--- /dev/null
+++ b/fit2cloud-view/目录结构.md
@@ -0,0 +1,33 @@
+# 目录结构
+
+```text
+├── public // 静态资源
+│ ├── favicon.icon // 图标
+│ └── index.html // 入口html
+│ ├── mock // 项目mock 模拟数据
+├── src // 源代码
+│ ├── api // 所有请求
+│ ├── assets // 主题 字体等静态资源
+│ ├── business // 业务组件
+│ ├── components // 全局公用组件
+│ ├── directive // 全局指令
+│ ├── filters // 全局 filter
+│ ├── icons // 项目所有 svg icons
+│ ├── lang // 国际化 language
+│ ├── plugins // Vue插件
+│ ├── router // 路由
+│ ├── store // 全局 store管理
+│ ├── styles // 全局样式
+│ ├── utils // 全局公用方法
+│ ├── App.vue // 入口应用组件
+│ ├── main.js // 入口js
+│ └── permission.js // 权限管理
+├── .editorconfig // 代码规范配置
+├── .gitignore // git 忽略项
+├── favicon.ico // favicon图标
+├── index.html // html模板
+├── vue.config.js // 构建配置
+└── package.json // package.json
+
+```
+