commit 5137a64cceda2d12de07cabe78f0520f95f5dceb Author: liangjiami <2249412933@qq.com> Date: Fri Jul 4 10:37:02 2025 +0800 第一次提交 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..e995065 --- /dev/null +++ b/.env.development @@ -0,0 +1,6 @@ +NODE_ENV = 'development' +# 接口版权 +VUE_APP_API_VERSION = 'admin' +VUE_APP_API_BASEURL = 'https://dev.api.tco211.com/' +VUE_APP_SSO_LOGINURL = 'https://dev.uc.f2b211.com/index.php/uc/user/login.html' +VUE_APP_SSO_APPID = '20211117000001117' \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..3e7bd59 --- /dev/null +++ b/.env.production @@ -0,0 +1,6 @@ +NODE_ENV = 'production' +# 接口版权 +VUE_APP_API_VERSION = 'admin' +VUE_APP_API_BASEURL = 'https://api.tco211.com/' +VUE_APP_SSO_LOGINURL = 'http://uc.f2b211.com/index.php/uc/user/login.html' +VUE_APP_SSO_APPID = '20211117000001117' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b76b4ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +.DS_Store +node_modules +/dist + + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +# .env.development +# .env.production +# package.json +package-lock.json +# vue.config +/.history diff --git a/README.md b/README.md new file mode 100644 index 0000000..c951a64 --- /dev/null +++ b/README.md @@ -0,0 +1,391 @@ +# orico_vue3_demo + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Lints and fixes files +``` +npm run lint +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). + + +### [vee-validate](https://vee-validate.logaretm.com/v4/) 验证 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
alphaalpha_dashalpha_numalpha_spaces
betweenconfirmeddimensionsemail
excludedextimageone_of
integerisis_notlength
maxmax_valuemimesmin
maxmax_valuemimesmin
min_valuenumericregexrequired
sizeurlmobiletel
identifydecimalletter_upperletter_lower
character_cnzipcodefax
+ +>1. alpha:包含字母字符 +``` + + + + + +``` + +>2. alpha_dash:包含字母字符、数字、破折号或下划线 +``` + + + + + +``` + +>3. alpha_num:包含字母字符或数字 +``` + + + + + +``` + +>4. alpha_spaces:包含字母字符或空格 +``` + + + + + +``` + +>5. between:字段必须具有由最小值和最大值限定的数值 +``` + + + + + + + + +``` + +>6. confirmed:验证的字段必须具有与确认字段相同的值 +``` +
+ + + + +``` + +>7. digits:字段必须是数字并且具有指定的位数 +``` + + + + + +``` + +>8. dimensions:字段的文件必须是具有确切指定维度的图像(jpg、 svg、 jpeg、 png、 bmp、 gif)。 +``` + + + + + + + + +``` + +>9. email:字段必须是有效的电子邮件 +``` + + + + + +``` + +>10. excluded:正在验证的字段必须有一个不在指定列表中的值 +``` + + + + + +``` + +>11. ext:字段的文件必须具有指定的扩展名之一 +``` + + + + + +``` + +>12. image:验证字段中的文件必须具有图像 mime 类型(image/*) +``` + + + + + +``` + +>13. integer:验证下的字段必须是有效的整数值。不接受指数表示法 +``` + + + + + +``` + +>14. is:验证下的字段必须匹配给定的值,并使用严格的相等性 +``` + + + + + +``` + +>15. is_not:验证下的字段必须与给定的值不匹配,并使用严格相等 +``` + + + + + +``` + +>16. length:验证下的字段必须具有指定的项数,只能用于迭代对象,允许的迭代值包括字符串、数组和任何可以与 Array.from 一起使用的对象 +``` + + + + + +``` + +>17. max:验证长度下的字段不能超过指定的长度 +``` + + + + + +``` + +>18. max_value:正在验证的字段必须是一个数值,并且不能大于指定的值 +``` + + + + + +``` + +>19. mimes:在验证下添加到字段的文件类型应该具有指定的 mime 类型之一 +``` + + + + + +``` + +>20. min:验证长度下的字段不应小于指定的长度 +``` + + + + + +``` + +>21. min_value:正在验证的字段必须是一个数值,并且不能小于指定的值 +``` + + + + + +``` + +>22. numeric:正在验证的字段必须只包含数字 +``` + + + + + +``` + +>23. one_of:正在验证的字段必须具有指定列表中的值 +``` + + + + + +``` + +>24. regex:验证下的字段必须与指定的正则表达式匹配 +``` + + + + +``` + +>25. required:正在验证的字段必须有一个非空值。默认情况下,如果验证器具有“空值”,则所有验证器都通过验证,除非它们是必需的。这些空值是空字符串、未定义、 null、 false 和空数组。 +``` + + + + + +``` + +>26. size:字段的文件大小不得超过指定的大小(单位为千字节) +``` + + + + + +``` + +>27. url:验证下的字段必须是有效的 url。如果你需要更多的限制,你可以传递一个模式。 +``` + + + + + +``` + +>29. mobile:验证是否为手机号 +``` + + +``` + +>30. tel:验证是否为电话 +``` + + +``` + +>31. identify: +``` + + +``` + +>32. decimal:验证小数位数 +``` + + +``` + +>33. letter_upper:是否为大写字母 +``` + + +``` + +>34. letter_lower:是否为小写字母 +``` + + +``` + +>35. character_cn:是否为中文字符 +``` + + +``` + +>36. zipcode:邮政编码格式验证 +``` + + +``` + +>37. fax:传真格式验证 +``` + + +``` \ No newline at end of file diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..e955840 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..918e7be --- /dev/null +++ b/package.json @@ -0,0 +1,80 @@ +{ + "name": "orico_vue3_demo", + "version": "0.1.0", + "private": true, + "scripts": { + "serve": "vue-cli-service serve --open --mode=development", + "build:dev": "vue-cli-service build --mode=development", + "build:pro": "vue-cli-service build --mode=production", + "build:test": "vue-cli-service build --mode test", + "lint": "vue-cli-service lint" + }, + "dependencies": { + "@element-plus/icons": "0.0.11", + "@vee-validate/i18n": "^4.4.7", + "@vee-validate/rules": "^4.4.7", + "axios": "^0.21.4", + "core-js": "^3.17.3", + "downloadjs": "^1.4.7", + "element-plus": "^1.1.0-beta.9", + "exceljs": "^4.3.0", + "file-saver": "^2.0.5", + "js-md5": "^0.7.3", + "moment": "^2.29.1", + "sass": "^1.1.0", + "script-loader": "^0.7.2", + "sortablejs": "^1.14.0", + "uglifyjs-webpack-plugin": "^2.2.0", + "unplugin-vue-components": "^0.15.3", + "vee-validate": "^4.4.11", + "vue": "^3.2.11", + "vue-draggable-next": "^2.1.1", + "vue-router": "^4.0.0-0", + "vuex": "^4.0.0-0", + "vxe-table": "^4.1.0", + "vxe-table-plugin-export-xlsx": "^4.0.7", + "xe-utils": "^3.4.0", + "xlsx": "^0.17.3" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "~4.5.0", + "@vue/cli-plugin-router": "~4.5.0", + "@vue/cli-plugin-vuex": "~4.5.0", + "@vue/cli-service": "~4.5.0", + "@vue/compiler-sfc": "^3.2.11", + "babel-eslint": "^10.1.0", + "babel-plugin-import": "^1.13.3", + "compression-webpack-plugin": "^6.1.1", + "eslint-plugin-vue": "^7.0.0", + "js-cookie": "^3.0.1", + "sass-loader": "^8.0.2", + "vue-loader-v16": "^16.0.0-beta.5.4" + }, + "eslintConfig": { + "root": true, + "env": { + "node": true + }, + "extends": [ + "plugin:vue/essential", + "eslint:recommended" + ], + "parserOptions": { + "parser": "babel-eslint" + }, + "rules": { + "vue/html-closing-bracket-spacing": "warn", + "vue/html-end-tags": "error", + "vue/jsx-uses-vars": "warn", + "vue/match-component-file-name": "error", + "vue/mustache-interpolation-spacing": "warn", + "vue/vue-unused-vars": "off", + "vue-unused-vars": "off" + } + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not dead" + ] +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..9b6d8e1 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..4123528 --- /dev/null +++ b/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..d96c083 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,53 @@ + + + + diff --git a/src/assets/daochu.png b/src/assets/daochu.png new file mode 100644 index 0000000..6c30158 Binary files /dev/null and b/src/assets/daochu.png differ diff --git a/src/assets/excel/Blob.js b/src/assets/excel/Blob.js new file mode 100644 index 0000000..6e8c495 --- /dev/null +++ b/src/assets/excel/Blob.js @@ -0,0 +1,141 @@ +/* eslint-disable */ +require('script-loader!file-saver'); +require('script-loader!./Blob'); +require('script-loader!xlsx/dist/xlsx.core.min'); +function generateArray(table) { + var out = []; + var rows = table.querySelectorAll('tr'); + var ranges = []; + for (var R = 0; R < rows.length; ++R) { + var outRow = []; + var row = rows[R]; + var columns = row.querySelectorAll('td'); + for (var C = 0; C < columns.length; ++C) { + var cell = columns[C]; + var colspan = cell.getAttribute('colspan'); + var rowspan = cell.getAttribute('rowspan'); + var cellValue = cell.innerText; + if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; + + //Skip ranges + ranges.forEach(function (range) { + if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { + for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); + } + }); + + //Handle Row Span + if (rowspan || colspan) { + rowspan = rowspan || 1; + colspan = colspan || 1; + ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}}); + } + ; + + //Handle Value + outRow.push(cellValue !== "" ? cellValue : null); + + //Handle Colspan + if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null); + } + out.push(outRow); + } + return [out, ranges]; +}; + +function datenum(v, date1904) { + if (date1904) v += 1462; + var epoch = Date.parse(v); + return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); +} + +function sheet_from_array_of_arrays(data, opts) { + var ws = {}; + var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}}; + for (var R = 0; R != data.length; ++R) { + for (var C = 0; C != data[R].length; ++C) { + if (range.s.r > R) range.s.r = R; + if (range.s.c > C) range.s.c = C; + if (range.e.r < R) range.e.r = R; + if (range.e.c < C) range.e.c = C; + var cell = {v: data[R][C]}; + if (cell.v == null) continue; + var cell_ref = XLSX.utils.encode_cell({c: C, r: R}); + + if (typeof cell.v === 'number') cell.t = 'n'; + else if (typeof cell.v === 'boolean') cell.t = 'b'; + else if (cell.v instanceof Date) { + cell.t = 'n'; + cell.z = XLSX.SSF._table[14]; + cell.v = datenum(cell.v); + } + else cell.t = 's'; + + ws[cell_ref] = cell; + } + } + if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); + return ws; +} + +function Workbook() { + if (!(this instanceof Workbook)) return new Workbook(); + this.SheetNames = []; + this.Sheets = {}; +} + +function s2ab(s) { + var buf = new ArrayBuffer(s.length); + var view = new Uint8Array(buf); + for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; + return buf; +} + +export function export_table_to_excel(id) { + var theTable = document.getElementById(id); + console.log('a') + var oo = generateArray(theTable); + var ranges = oo[1]; + + /* original data */ + var data = oo[0]; + var ws_name = "SheetJS"; + console.log(data); + + var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); + + /* add ranges to worksheet */ + // ws['!cols'] = ['apple', 'banan']; + ws['!merges'] = ranges; + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'}); + + saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx") +} + +function formatJson(jsonData) { + console.log(jsonData) +} +export function export_json_to_excel(th, jsonData, defaultTitle) { + + /* original data */ + + var data = jsonData; + data.unshift(th); + var ws_name = "SheetJS"; + + var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); + + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'}); + var title = defaultTitle || '列表' + saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx") +} \ No newline at end of file diff --git a/src/assets/excel/Export2Excel.js b/src/assets/excel/Export2Excel.js new file mode 100644 index 0000000..38c6c97 --- /dev/null +++ b/src/assets/excel/Export2Excel.js @@ -0,0 +1,219 @@ +import { saveAs } from 'file-saver' +import XLSX from 'xlsx' + +function generateArray(table) { + var out = []; + var rows = table.querySelectorAll('tr'); + var ranges = []; + for (var R = 0; R < rows.length; ++R) { + var outRow = []; + var row = rows[R]; + var columns = row.querySelectorAll('td'); + for (var C = 0; C < columns.length; ++C) { + var cell = columns[C]; + var colspan = cell.getAttribute('colspan'); + var rowspan = cell.getAttribute('rowspan'); + var cellValue = cell.innerText; + if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; + + //Skip ranges + ranges.forEach(function (range) { + if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { + for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); + } + }); + + //Handle Row Span + if (rowspan || colspan) { + rowspan = rowspan || 1; + colspan = colspan || 1; + ranges.push({ + s: { + r: R, + c: outRow.length + }, + e: { + r: R + rowspan - 1, + c: outRow.length + colspan - 1 + } + }); + }; + + //Handle Value + outRow.push(cellValue !== "" ? cellValue : null); + + //Handle Colspan + if (colspan) + for (var k = 0; k < colspan - 1; ++k) outRow.push(null); + } + out.push(outRow); + } + return [out, ranges]; +}; + +function datenum(v, date1904) { + if (date1904) v += 1462; + var epoch = Date.parse(v); + return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); +} + +function sheet_from_array_of_arrays(data, opts) { + var ws = {}; + var range = { + s: { + c: 10000000, + r: 10000000 + }, + e: { + c: 0, + r: 0 + } + }; + for (var R = 0; R != data.length; ++R) { + for (var C = 0; C != data[R].length; ++C) { + if (range.s.r > R) range.s.r = R; + if (range.s.c > C) range.s.c = C; + if (range.e.r < R) range.e.r = R; + if (range.e.c < C) range.e.c = C; + var cell = { + v: data[R][C] + }; + if (cell.v == null) continue; + var cell_ref = XLSX.utils.encode_cell({ + c: C, + r: R + }); + + if (typeof cell.v === 'number') cell.t = 'n'; + else if (typeof cell.v === 'boolean') cell.t = 'b'; + else if (cell.v instanceof Date) { + cell.t = 'n'; + cell.z = XLSX.SSF._table[14]; + cell.v = datenum(cell.v); + } else cell.t = 's'; + + ws[cell_ref] = cell; + } + } + if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); + return ws; +} + +function Workbook() { + if (!(this instanceof Workbook)) return new Workbook(); + this.SheetNames = []; + this.Sheets = {}; +} + +function s2ab(s) { + var buf = new ArrayBuffer(s.length); + var view = new Uint8Array(buf); + for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; + return buf; +} + +export function export_table_to_excel(id) { + var theTable = document.getElementById(id); + var oo = generateArray(theTable); + var ranges = oo[1]; + + /* original data */ + var data = oo[0]; + var ws_name = "SheetJS"; + + var wb = new Workbook(), + ws = sheet_from_array_of_arrays(data); + + /* add ranges to worksheet */ + // ws['!cols'] = ['apple', 'banan']; + ws['!merges'] = ranges; + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, { + bookType: 'xlsx', + bookSST: false, + type: 'binary' + }); + + saveAs(new Blob([s2ab(wbout)], { + type: "application/octet-stream" + }), "test.xlsx") +} + +export function export_json_to_excel({ + multiHeader = [], + header, + data, + filename, + merges = [], + autoWidth = true, + bookType = 'xlsx' +} = {}) { + /* original data */ + filename = filename || 'excel-list' + data = [...data] + data.unshift(header); + + for (let i = multiHeader.length - 1; i > -1; i--) { + data.unshift(multiHeader[i]) + } + + var ws_name = "SheetJS"; + var wb = new Workbook(), + ws = sheet_from_array_of_arrays(data); + + if (merges.length > 0) { + if (!ws['!merges']) ws['!merges'] = []; + merges.forEach(item => { + ws['!merges'].push(XLSX.utils.decode_range(item)) + }) + } + + if (autoWidth) { + /*设置worksheet每列的最大宽度*/ + const colWidth = data.map(row => row.map(val => { + /*先判断是否为null/undefined*/ + if (val == null) { + return { + 'wch': 10 + }; + } + /*再判断是否为中文*/ + else if (val.toString().charCodeAt(0) > 255) { + return { + 'wch': val.toString().length * 2 + }; + } else { + return { + 'wch': val.toString().length + }; + } + })) + /*以第一行为初始值*/ + let result = colWidth[0]; + for (let i = 1; i < colWidth.length; i++) { + for (let j = 0; j < colWidth[i].length; j++) { + if (result[j]['wch'] < colWidth[i][j]['wch']) { + result[j]['wch'] = colWidth[i][j]['wch']; + } + } + } + ws['!cols'] = result; + } + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, { + bookType: bookType, + bookSST: false, + type: 'binary' + }); + saveAs(new Blob([s2ab(wbout)], { + type: "application/octet-stream" + }), `${filename}.${bookType}`); +} \ No newline at end of file diff --git a/src/assets/icon/iconfont.ttf b/src/assets/icon/iconfont.ttf new file mode 100644 index 0000000..f2d0a59 Binary files /dev/null and b/src/assets/icon/iconfont.ttf differ diff --git a/src/assets/icon/iconfont.woff b/src/assets/icon/iconfont.woff new file mode 100644 index 0000000..ebfb238 Binary files /dev/null and b/src/assets/icon/iconfont.woff differ diff --git a/src/assets/icon/iconfont.woff2 b/src/assets/icon/iconfont.woff2 new file mode 100644 index 0000000..277531a Binary files /dev/null and b/src/assets/icon/iconfont.woff2 differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..89e9ab1 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/style/global.scss b/src/assets/style/global.scss new file mode 100644 index 0000000..2b65889 --- /dev/null +++ b/src/assets/style/global.scss @@ -0,0 +1,260 @@ +html, +body { + margin: 0; + padding: 0; + color: #333; + box-sizing: border-box; +} + +a { + text-decoration: none; +} + +/*去掉项目标签*/ +ul { + list-style-type: none; +} + +.el-main { + padding: 0px !important; +} + +.main-container { + width: -moz-available; + width: -webkit-fill-available; + width: stretch; + height: -moz-available; + height: -webkit-fill-available; + height: stretch; + height: 100% !important; +} + +.min-height-fill4parent { + height: -moz-available; + height: -webkit-fill-available; + height: stretch; +} + +/*成本管理样式*/ +.bg_white { + background-color: #FFF +} + +.m-t-20 { + margin-top: 20px +} + +.m-t-10 { + margin-top: 10px +} + +.table_90 { + width: -moz-calc(100% - 24px); + width: -webkit-calc(100% - 24px); + width: calc(100% - 24px); + margin-left: 12px; + margin-right: 12px; +} + +.p-t-10 { + padding-top: 10px; +} + +.p-b-10 { + padding-bottom: 10px; +} + +.border-r-10 { + border-radius: 10px; +} + +.text-black { + color: #606266; +} + +.text-red { + color: #e73235; +} + +.text-green { + color: #38b48b; +} + +.m-b-90 { + margin-bottom: 90px; +} + +.m-t-10 { + margin-top: 10px; +} + +.m-b-20 { + margin-bottom: 20px; +} + +.p-t-20 { + padding-top: 20px; +} + +.p-b-20 { + padding-bottom: 20px; +} + +/*字体居中*/ +.text-l { + text-align: left; +} + +.text-c { + text-align: center; +} + +.text-r { + text-align: right; +} + +.f-12 { + font-size: 12px; +} + +.header-title { + height: 0; +} + +.svg-icon-custom { + width: 16px; + height: 16px; + fill: currentColor; + overflow: hidden; + margin-right: 10px +} + +label { + font-weight: bold; +} + +/*图片放大样式*/ +/*定位*/ +.position-r { + position: relative +} + +.position-a { + position: absolute; +} + +.position-f { + position: fixed; +} + +/*知识产权样式*/ +.m-20 { + margin: 20px; +} + +.p-20 { + padding: 20px; +} + +.m-l-10 { + margin-left: 10px; +} + +.m-r-10 { + margin-right: 10px; +} + +.p-t-20 { + padding-top: 20px; +} + +.p-b-20 { + padding-bottom: 20px; +} + +.p-l-40 { + padding-left: 40px; +} + +.p-r-40 { + padding-right: 40px; +} + +.m-l-40 { + margin-left: 40px; +} + +.m-r-20 { + margin-right: 20px; +} + +.m-r-40 { + margin-right: 40px; +} + +.text_blue { + color: #2c74fa; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.Cursor { + cursor: pointer; +} + +.footer { + position: fixed; + bottom: 0; + z-index: 5; + text-align: right; + right: 0; + box-shadow: -1px 0px 15px 0px #c9ccd4; +} + +.search_button { + float: right; + margin-right: 60px; +} + +.tips { + // margin-left: 120px; + font-size: 12px; + line-height: 24px; + color: #999999; + margin: 0 !important; +} + +.msgbox { + width: 25%; + max-height: 70%; +} + +.el-table__body-wrapper::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.el-table__body-wrapper::-webkit-scrollbar-thumb { + border-radius: 6px; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-thumb { + background-color: #a1a3a9; + border-radius: 6px; +} + +.el-table--fluid-height .el-table__fixed, +.el-table--fluid-height .el-table__fixed-right { + bottom: 8px !important; +} \ No newline at end of file diff --git a/src/assets/style/icon.scss b/src/assets/style/icon.scss new file mode 100644 index 0000000..80b8fd3 --- /dev/null +++ b/src/assets/style/icon.scss @@ -0,0 +1,13 @@ +@font-face { + font-family: 'icon-font'; + src: url('../icon/iconfont.woff2') format('woff2'), + url('../icon/iconfont.woff') format('woff'), + url('../icon/iconfont.ttf') format('truetype'); +} +.icon-font { + font-family: "icon-font" !important; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + diff --git a/src/assets/tianjia.png b/src/assets/tianjia.png new file mode 100644 index 0000000..498c556 Binary files /dev/null and b/src/assets/tianjia.png differ diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..e98d632 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,4 @@ +import Layout from './layout/index' +export { + Layout +} diff --git a/src/components/layout/index.vue b/src/components/layout/index.vue new file mode 100644 index 0000000..5a5b1d4 --- /dev/null +++ b/src/components/layout/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/layout/libs/index.js b/src/components/layout/libs/index.js new file mode 100644 index 0000000..96e21ca --- /dev/null +++ b/src/components/layout/libs/index.js @@ -0,0 +1,8 @@ +import LayoutAside from './layout-aside/index.vue' +import LayoutHeader from './layout-header/index.vue' +import LayoutFooter from './layout-footer/index.vue' +export { + LayoutAside, + LayoutHeader, + LayoutFooter +} diff --git a/src/components/layout/libs/layout-aside/index.vue b/src/components/layout/libs/layout-aside/index.vue new file mode 100644 index 0000000..853af27 --- /dev/null +++ b/src/components/layout/libs/layout-aside/index.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/components/layout/libs/layout-aside/item.vue b/src/components/layout/libs/layout-aside/item.vue new file mode 100644 index 0000000..cbb3043 --- /dev/null +++ b/src/components/layout/libs/layout-aside/item.vue @@ -0,0 +1,129 @@ + + + + diff --git a/src/components/layout/libs/layout-footer/index.vue b/src/components/layout/libs/layout-footer/index.vue new file mode 100644 index 0000000..4ba268c --- /dev/null +++ b/src/components/layout/libs/layout-footer/index.vue @@ -0,0 +1,6 @@ + + diff --git a/src/components/layout/libs/layout-header/index.vue b/src/components/layout/libs/layout-header/index.vue new file mode 100644 index 0000000..830b105 --- /dev/null +++ b/src/components/layout/libs/layout-header/index.vue @@ -0,0 +1,319 @@ + + + diff --git a/src/icons/index.js b/src/icons/index.js new file mode 100644 index 0000000..2c6b309 --- /dev/null +++ b/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/src/main.js b/src/main.js new file mode 100644 index 0000000..d395fba --- /dev/null +++ b/src/main.js @@ -0,0 +1,64 @@ +import { + createApp +} from 'vue' +import ElementPlus from 'element-plus' +import App from './App.vue' +import router from './router' +import store from './store' +import Request from '@/utils/request' +import '@/router/permission.js' +import 'element-plus/dist/index.css' +import { + ElMessage +} from 'element-plus' +import locale from 'element-plus/lib/locale/lang/zh-cn' //需要新加的代码 + +import { + defineRule, + configure +} from 'vee-validate' +import all from '@vee-validate/rules' +import allValid from '@/utils/validate' +import '@/assets/style/global.scss' +import '@/assets/style/icon.scss' +import '@/utils/iconfont.js' + +import 'xe-utils' +import VXETable from 'vxe-table' +import 'vxe-table/lib/style.css' + +configure({ + validateOnBlur: true, // controls if `blur` events should trigger validation with `handleChange` handler + validateOnChange: true, // controls if `change` events should trigger validation with `handleChange` handler + validateOnInput: false, // controls if `input` events should trigger validation with `handleChange` handler + validateOnModelUpdate: true // controls if `update:modelValue` events should trigger validation with `handleChange` handler +}) +Object.keys(all).forEach(rule => { + defineRule(rule, all[rule]) +}) +Object.keys(allValid).forEach(rule => { + defineRule(rule, allValid[rule]) +}) + +const app = createApp(App) +app.use(store) +app.use(router) +app.use(ElMessage) +app.use(ElementPlus, { + locale +}) //需要改变的地方,加入locale +app.use(VXETable) + +app.config.globalProperties.$http = new Request() +// app.config.globalProperties.$message = ElMessage +app.mount('#app') + +// 注册一个全局自定义指令 `v-allow` 配合路由meta.permission信息控制节点显示权限 +app.directive('allow', { + mounted(el, binding) { + const permission = binding.instance.$route.meta.permission + if (!permission.includes(binding.value)) { + el.parentNode.removeChild(el) + } + } +}) \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..66ac485 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,55 @@ +import { + createRouter, + createWebHistory +} from 'vue-router' +import { + Layout +} from '@/components/' + +export const staticRoutes = [ + { + path: '/', + name: 'Main', + component: Layout, + meta: { + title: '老成本管理', + icon: '' + }, + hidden: true, + children: [{ + path: '/oindex', + name: 'NewCostList', + meta: { + title: '老成本管理', + icon: 'el-icon-s-home' + }, + hidden: true, + component: () => import('@/views/index/oindex') + }] + }, + { + path: '/login', + name: 'Login', + hidden: true, + component: () => import('@/views/login/index'), + meta: { + title: '登录页' + } + }, + { + path: '/404', + name: '404', + hidden: true, + component: () => import('@/views/public/404'), + meta: { + title: '页面未找到' + } + } +] + +const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: staticRoutes +}) + +export default router diff --git a/src/router/permission.js b/src/router/permission.js new file mode 100644 index 0000000..5260134 --- /dev/null +++ b/src/router/permission.js @@ -0,0 +1,55 @@ +import router from '@/router' +import store from '@/store' +import { title as settingsTitle } from '@/settings' +import { checkSignedInStatus, setNotLoggedIn } from '@/utils/auth' + +let loadedDynamicRoutes = false // 是否已加载动态路由 +const whiteList = ['/login'] +router.beforeEach(async (to, from, next) => { + // 网页title跟随路由title + document.title = settingsTitle + '-' + to.meta.title + + if (checkSignedInStatus()) { + // 身份验证通过 + if (to.path !== '/login') { // 不在登录页面,正常进入相应页面 + if (loadedDynamicRoutes) { + store.dispatch('topNavTag/addNavTag', to) // 顶部页面标签处理 + next() + } else { + + try { + // 获取用户信息 + await store.dispatch('user/getUserInfo') + // 获取用户权限列表 + const authList = store.getters['user/getUserAuthList'] + // 获取用户有权限操作权限的路由 + const accessRoutes = await store.dispatch('routes/generateRoutes', authList) + // 动态添加路由 + accessRoutes.forEach(route => { + router.addRoute(route) + }) + + // 设为已加载动态路由 + loadedDynamicRoutes = true + + // 确保动态添加的路由已经被完全加载上去并替换浏览器路由使浏览器没有浏览历史可回退 + next({ ...to, replace: true }) + } catch (error) { + // 设为非登录状态 + setNotLoggedIn() + next('/login') + } + } + } else { // 在登录页面 + next({ path: '/' }) + } + } else { // 身份验证不过 + // 设为非登录状态 + setNotLoggedIn() + if (whiteList.indexOf(to.path) !== -1) { // 处于不用登录页面 + next() + } else { + next('/login') + } + } +}) diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..280a718 --- /dev/null +++ b/src/settings.js @@ -0,0 +1,3 @@ +module.exports = { + title: 'OPD211' +} diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..efca841 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,19 @@ +import { + createStore +} from 'vuex' +import leftAside from './modules/leftAside' +import topNavTag from './modules/topNavTag' +import routes from './modules/routes' +import user from './modules/user' + +export default createStore({ + state: {}, + mutations: {}, + actions: {}, + modules: { + leftAside, + topNavTag, + routes, + user + } +}) diff --git a/src/store/modules/leftAside.js b/src/store/modules/leftAside.js new file mode 100644 index 0000000..e008e78 --- /dev/null +++ b/src/store/modules/leftAside.js @@ -0,0 +1,28 @@ +const state = { + leftMenuWidth: '200px', + isCollapse: false +} + +const mutations = { + CHANGE_COLLAPSE: (state) => { + state.isCollapse = !state.isCollapse + if (state.isCollapse) { + state.leftMenuWidth = 'auto' + } else { + state.leftMenuWidth = '200px' + } + } +} + +const actions = { + changeCollapse (context) { + context.commit('CHANGE_COLLAPSE') + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/store/modules/routes.js b/src/store/modules/routes.js new file mode 100644 index 0000000..5227a6d --- /dev/null +++ b/src/store/modules/routes.js @@ -0,0 +1,84 @@ +import { staticRoutes } from '@/router' +import Request from '@/utils/request' +import Layout from '@/components/layout/index' + +// 从服务器获取菜单 +async function fetchAsyncRoutes4Server() { + return (new Request()).mock(true).get('menu/index').then(r => { + if (r.errno !== 0) { + return [] + } + let ar =[{"id":616,"pid":0,"menu_title":"OPD211","menu_name":"NewCost","menu_path":"\/index","menu_component":"\/layout","redirect":"","sort":1,"type":1,"is_menu":1,"component":"\/layout","name":"NewCost","path":"\/index","title":"OPD211","meta":{"title":"OPD211"},"hidden":false,"alwaysshow":true,"children":[{"id":617,"pid":616,"menu_title":"老成本管理","menu_name":"NewCostList","menu_path":"\/index\/index","menu_component":"\/index\/index","redirect":"","sort":2,"type":1,"is_menu":1,"component":"\/index\/index","name":"NewCostList","path":"\/index\/index","title":"老成本管理","meta":{"title":"老成本管理"},"hidden":false,"alwaysshow":false},{"id":619,"pid":616,"menu_title":"日志管理","menu_name":"NewCostLog","menu_path":"\/index\/log","menu_component":"\/index\/log","redirect":"","sort":4,"type":1,"is_menu":1,"component":"\/index\/log","name":"NewCostLog","path":"\/index\/log","title":"日志管理","meta":{"title":"日志管理"},"hidden":true,"alwaysshow":false}]}] + // ar.children.push(r.data.children[2]) + console.log(ar) + return ar + }) + // return ar +} + +// 组装路由结构 +function buildTreeAsyncRoutes (routes, authList) { + const menus = [] + routes.forEach(item => { + const it = { id: item.id, path: item.path, name: item.name, meta: item.meta, alwaysShow: item.alwaysShow, hidden: item.hidden } + if (item.redirect != null) { + it.redirect = item.redirect + } + if (item.component && authList.indexOf(item.id) >= 0) { + if (item.component === '/layout') { + it.component = Layout + } else { + it.component = () => import('@/views' + item.component) + } + if (item.children && item.children.length) { + item.children.forEach(v => { + if (!it.meta.permission) { + it.meta.permission = [] + } + if (item.path.startsWith('/')) { + item.path = item.path.substring(1) + } + if (authList.indexOf(v.id) >= 0) { + it.meta.permission.push(item.name + '.' + v.name) + } + }) + it.children = buildTreeAsyncRoutes(item.children, authList) + } + menus.push(it) + + } + }) + return menus + +} + +const state = { + routes: [] +} + +const mutations = { + SET_ROUTES: (state, routes) => { + state.routes = routes + } +} + +const actions = { + async generateRoutes (context, authList) { + // 服务器获取菜单 + const routes = await fetchAsyncRoutes4Server() + // 权限过滤 + // const accessRoutes = routes.filter(v => authList.indexOf(v.id) >= 0) + // 组装路由 + const treeRoutes = buildTreeAsyncRoutes(routes, authList) + const mergeTreeRoutes = staticRoutes.concat(treeRoutes) + context.commit('SET_ROUTES', mergeTreeRoutes) + return mergeTreeRoutes + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/store/modules/topNavTag.js b/src/store/modules/topNavTag.js new file mode 100644 index 0000000..05aca74 --- /dev/null +++ b/src/store/modules/topNavTag.js @@ -0,0 +1,68 @@ +import router from '@/router' +const state = { + tags: [ + // 默认打开路由标签 + { fullPath: '/index/index', name: 'NewCostList', meta: { title: '成本管理' } } + ] // 标签 +} + +const mutations = { + // 新增标签 + ADD_NAV_TAG: (state, tag) => { + state.tags.push(tag) + }, + + // 替换标签 + REPLACE_NAV_TAG: (state, tag) => { + const index = state.tags.findIndex(it => it.fullPath === tag.fullPath) + // const index = state.tags.findIndex(it => it.path === tag.path) + state.tags.splice(index, 1, tag) + }, + + // 删除标签 + RM_NAV_TAG: (state, index) => { + state.tags = state.tags.filter((it, i) => { + return i !== index || it.name === 'NewCostList' + }) + } +} + +const actions = { + // 新增标签 + addNavTag: (context, tag) => { + // 跳转至新打开页面 + if (tag.name == '404') { + return false + } + // router.push({ path: tag.fullPath }) + // if (!context.state.tags.some(v => v.name === tag.name)) { + if (!context.state.tags.some(v => v.fullPath === tag.fullPath)) { + // 标签栏没有,新增页面标签 + context.commit('ADD_NAV_TAG', tag) + } else { + // 标签栏有,替换标签 + context.commit('REPLACE_NAV_TAG', tag) + } + }, + + // 删除标签 + removeNavTag: (context, index) => { + let nextIndex = index - 1 + var overallVar = context + if (context.state.tags.length - 1 > index) { + nextIndex = index + 1 + } + // 最后一个页面标签不允许删除 + if (nextIndex === -1) return false + // router.push({ path: context.state.tags[nextIndex].path }) + router.push(context.state.tags[nextIndex]); + context.commit('RM_NAV_TAG', index) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/store/modules/user.js b/src/store/modules/user.js new file mode 100644 index 0000000..6f31ac8 --- /dev/null +++ b/src/store/modules/user.js @@ -0,0 +1,68 @@ +import { toRaw } from 'vue' +import Request from '@/utils/request' + +const state = { + userInfo: {} +} + +const getters = { + // 获取用户菜单权限 + getUserAuthList: state => { + if (state.userInfo.length <= 0) { + return [] + } + if (typeof state.userInfo.auth_list === 'undefined') { + return [] + } + return toRaw(state.userInfo.auth_list) + } +} + +const mutations = { + SET_USER_INFO: (state, user) => { + state.userInfo = user + } +} + +const actions = { + // 设置用户信息 + setUserInfo (context, user) { + context.commit('SET_USER_INFO', user) + }, + + // 获取用户信息 + getUserInfo (context) { + return new Promise((resolve, reject) => { + if (context.state.userInfo.length <= 0 || typeof context.state.userInfo.auth_list === 'undefined') { + // 服务器获取用户信息 + return (new Request()).get('login/getOssInfo') + .then(r => { + const { errno, data, errmsg } = r + if (errno !== 0) { + if (errno == 500){ + alert(errmsg) + setTimeout(() => window.location.href="/login") + }else{ + reject(errmsg) + } + + } + context.commit('SET_USER_INFO', data) + resolve(data) + }) + .catch(e => { + reject(e) + }) + } + resolve(toRaw(context.state.userInfo)) + }) + } +} + +export default { + namespaced: true, + state, + getters, + mutations, + actions +} diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..156596f --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,16 @@ +import Cookies from 'js-cookie' + +const signedInKey = 's_pass' +// 设置已登录状态cookie +export function setSignedIn () { + return Cookies.set(signedInKey, 1) +} +// 判断是否已登录 +export function checkSignedInStatus () { + return (Cookies.get(signedInKey) * 1) === 1 +} +// 删除登录状态cookie +export function setNotLoggedIn () { + sessionStorage.clear() + return Cookies.remove(signedInKey) +} diff --git a/src/utils/common.js b/src/utils/common.js new file mode 100644 index 0000000..d29fa20 --- /dev/null +++ b/src/utils/common.js @@ -0,0 +1,69 @@ +// 深拷贝对象 +export function copyObj(obj) { + if (!isObject(obj)) { + throw new Error('obj 不是一个对象!') + } + + const isArray = Array.isArray(obj) + const cloneObj = isArray ? [] : {} + for (const key in obj) { + cloneObj[key] = isObject(obj[key]) ? copyObj(obj[key]) : obj[key] + } + + return cloneObj +} + +// 赋值对象 +export function assignObj(obj1, obj2) { + for (const key in obj1, obj2) { + if (isObject(obj2[key])) { + assignObj(obj1[key], obj2[key]) + } else { + obj1[key] = obj2[key] + } + } +} + +export function isObject(o) { + return (typeof o === 'object' || typeof o === 'function') && o !== null +} + +//转义 +export function HTMLDecode(str){ + var s = '' + if (str.length == 0) return '' + s = str.replace(/</g, '<') + s = s.replace(/&/g, '&') + s = s.replace(/>/g, '>') + s = s.replace(/'/g, "\'") + s = s.replace(/"/g, '"') + return s +} + +export function HTMLEncode(str){ + var s = '' + if (str.length == 0) return '' + s = str.replace(/&/g, '&') + s = s.replace(//g, '>') + s = s.replace(/\'/g, ''') + s = s.replace(/\"/g, '"') + return s +} +//正则 +export function dealInputVal(value) { + value = value.replace(/^0*(0\.|[1-9])/, "$1"); + value = value.replace(/[^\d.]/g, ""); //清除"数字"和"."以外的字符 + value = value.replace(/^\./g, ""); //验证第一个字符是数字而不是字符 + value = value.replace(/\.{1,}/g, "."); //只保留第一个.清除多余的 + value = value + .replace(".", "$#$") + .replace(/\./g, "") + .replace("$#$", "."); + value = value.replace(/^()*(\d*)\.(\d\d).*$/, "$1$2.$3"); //只能输入两个小数 + value = + value.indexOf(".") > 0 + ? value.split(".")[0].substring(0, 10) + "." + value.split(".")[1] + : value.substring(0, 10); + return value; + } \ No newline at end of file diff --git a/src/utils/exportExcel.js b/src/utils/exportExcel.js new file mode 100644 index 0000000..7387098 --- /dev/null +++ b/src/utils/exportExcel.js @@ -0,0 +1,32 @@ +import axios from 'axios' + +const exportExcel = (url, params) => { + console.log('我是公共的方法' + url) + const apiVersion = process.env.VUE_APP_API_VERSION.replace(/\./g, '_') + axios.post(process.env.VUE_APP_API_BASEURL + apiVersion + url, params, { + responseType: 'blob', + headers: { + // 'X-Token': getToken(), + 'Content-Type': 'application/json;charset=utf-8' + } + }).then(res => { + if (!res) { + this.$message.error('下载模板文件失败') + return false + } + const stream = res.data // 后端用stream返回Excel文件 + const blob = new Blob([stream]) + // 前端获取业务码,成功执行正常业务 + const downloadElement = document.createElement('a') + const href = window.URL.createObjectURL(blob) // 创建下载的链接 + downloadElement.href = href + const timeStamp = new Date().toString() + const nameStr = decodeURI(escape(JSON.parse(res.headers['content-disposition'].split(';')[1].split('=')[1]))) + downloadElement.download = nameStr // 下载后文件名 + document.body.appendChild(downloadElement) + downloadElement.click() // 点击下载 + document.body.removeChild(downloadElement) // 下载完成移除元素 + window.URL.revokeObjectURL(href) // 释放掉blob对象 + }) +} +export { exportExcel } \ No newline at end of file diff --git a/src/utils/iconfont.js b/src/utils/iconfont.js new file mode 100644 index 0000000..a4cf0e8 --- /dev/null +++ b/src/utils/iconfont.js @@ -0,0 +1 @@ +!function(a){var l,h,t,i,c,C='',o=(o=document.getElementsByTagName("script"))[o.length-1].getAttribute("data-injectcss"),p=function(a,l){l.parentNode.insertBefore(a,l)};if(o&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}function d(){c||(c=!0,t())}function e(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(e,50)}d()}l=function(){var a,l;(l=document.createElement("div")).innerHTML=C,C=null,(a=l.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",l=a,(a=document.body).firstChild?p(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),l()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(t=l,i=a.document,c=!1,e(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,d())})}(window); \ No newline at end of file diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..4830c04 --- /dev/null +++ b/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/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..32aaf0f --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,141 @@ +import axios from 'axios' +import router from '@/router' +import { + ElLoading, + ElMessage +} from 'element-plus' +import { + setNotLoggedIn +} from '@/utils/auth' + +export default class Request { + http + isMock = false + isJWT = false + loadingInstance + loading = true + constructor() { + const apiVersion = process.env.VUE_APP_API_VERSION.replace(/\./g, '_') + this.http = axios.create({ + baseURL: process.env.VUE_APP_API_BASEURL + apiVersion + '/', + timeout: 600000, + withCredentials: true + }) + + this.interceptors() + } + + mock(mock) { + this.isMock = mock + return this + } + + JWT() { + this.isJWT = true + // this.http.defaults.headers.common['isJWT'] = true; + } + + showLoading(show) { + this.loading = show + return this + } + + post(path, params) { + let config = {} + if (this.isMock) config = { baseURL: process.env.VUE_APP_API_MOCK } + return new Promise((resolve, reject) => { + this.http.post(path, params, config).then((r) => { + resolve(r) + }).catch((e) => { + reject(e.message) + }) + }) + } + + get(path, params) { + const data = { + params: params + } + if (this.isMock) data.baseURL = process.env.VUE_APP_API_MOCK + return new Promise((resolve, reject) => { + this.http.get(path, data).then((r) => { + resolve(r) + }).catch((e) => { + reject(e.message) + }) + }) + } + + put(path, params) { + let config = {} + if (this.isMock) config = { baseURL: process.env.VUE_APP_API_MOCK } + return new Promise((resolve, reject) => { + this.http.put(path, params, config).then((r) => { + resolve(r) + }).catch((e) => { + reject(e.message) + }) + }) + } + + delete(path) { + let config = {} + if (this.isMock) config = { baseURL: process.env.VUE_APP_API_MOCK } + return new Promise((resolve, reject) => { + this.http.delete(path, config).then((r) => { + resolve(r) + }).catch((e) => { + reject(e.message) + }) + }) + } + + interceptors() { + this.http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8' + // 添加请求拦截器 + this.http.interceptors.request.use((config) => { + // 显示loading + if (this.loading) { + this.loadingInstance = ElLoading.service({ + fullscreen: true + }) + } + + if (this.isJWT) { + config.headers.Authorization = 'token' + } + return config + }, (error) => { + // 请求错误 + return Promise.reject(error) + }) + + // 添加响应拦截器 + this.http.interceptors.response.use((response) => { + // 关闭loading + if (this.loading) this.loadingInstance.close() + if (response.status === 200) { + if(response.data.status !== 200 && response.data.status !== 99999){ + if(response.data.message){ + ElMessage.error(response.data.message) + } + } + if (response.data.errno === 2003) { + setNotLoggedIn() // 设置未登录 + router.replace('/login') + return + } + return response.data + } else { + console.error(response) + } + }, (error) => { + // 关闭loading + setTimeout(() => { + if (this.loading) this.loadingInstance.close() + }, 3000) + // 响应错误 + return Promise.reject(error) + }) + } +} diff --git a/src/utils/storage.js b/src/utils/storage.js new file mode 100644 index 0000000..f0cbdf5 --- /dev/null +++ b/src/utils/storage.js @@ -0,0 +1,33 @@ +// session操作 +const sessionData = function (method, url, name, obj) { + /* + * 参数说明: + * method:get获取,set存入或覆盖,clean清除 + * name:session的名称 + * obj:存入的内容,可以是任意类型 + * */ + switch (method) { + case 'get': + if (sessionStorage.getItem(url + name + '_obj')) { + return JSON.parse(sessionStorage.getItem(url + name + '_obj')); + } else if (sessionStorage.getItem(url + name + '_str')) { + return sessionStorage.getItem(url + name + '_str'); + } else { + return null + } + case 'set': + sessionData('clean', url + name); + if (typeof obj == 'object') { + sessionStorage.setItem(url+ name + '_obj', JSON.stringify(obj)); + } else { + sessionStorage.setItem(url + name + '_str', obj); + } + return true; + case 'clean': + sessionStorage.removeItem(url + name + '_obj'); + sessionStorage.removeItem(url + name + '_str'); + return true; + } +}; + +export {sessionData} diff --git a/src/utils/upload.js b/src/utils/upload.js new file mode 100644 index 0000000..859679f --- /dev/null +++ b/src/utils/upload.js @@ -0,0 +1,58 @@ +// An highlighted block +import COS from 'cos-js-sdk-v5' +import { getSecret } from '@/api/upload' + +export default function upload(file, config, callback) { + if (typeof config.isPublic === 'undefined') { + config.isPublic = true + } + getSecret({ + type: config.type, + path: config.path, + isPublic: config.isPublic ? config.isPublic : false + }).then(response => { + const data = response.data + const cos = new COS({ + getAuthorization: function(options, callback) { + callback({ + TmpSecretId: data.tmpSecretId, + TmpSecretKey: data.tmpSecretKey, + XCosSecurityToken: data.sessionToken, + ExpiredTime: data.expiredTime + }) + } + }) + + return { + cos: cos, + dir: data.url, + bucket: data.bucket, + region: data.region + } + }).then(data => { + const cos = data.cos + const dir = data.dir + const bucket = data.bucket + const region = data.region + + const url = cos.getObjectUrl({ + Bucket: bucket, + Region: region, + Key: `${dir}/${file.name}`, + Sign: false + }, function(data) { + cos.putObject({ + Bucket: bucket, + Region: region, + Key: `${dir}/${file.name}`, + Body: file + }, function(data) { + if (config.isPublic) { + callback(url.replace(/^http(s)?:\/\/(.*?)\//, 'https://www.baidu.com/')) + } else { + callback(url) + } + }) + }) + }) +} diff --git a/src/utils/util.js b/src/utils/util.js new file mode 100644 index 0000000..235c077 --- /dev/null +++ b/src/utils/util.js @@ -0,0 +1,79 @@ +const formatDate = function (time, format) { + var t = new Date(time) + var tf = function(i) { + return (i < 10 ? "0" : "") + i + } + return format.replace(/Y|m|d|H|i|s/g, function(a) { + switch (a) { + case "Y": + return tf(t.getFullYear()) + case "m": + return tf(t.getMonth() + 1) + case "d": + return tf(t.getDate()) + case "H": + return tf(t.getMinutes()) + case "i": + return tf(t.getHours()) + case "s": + return tf(t.getSeconds()) + } + }) +} +// 时间戳转日期 +const timestampToTime = function (timestamp, type) { + var date = new Date(timestamp * 1000) // 时间戳为10位需*1000,时间戳为13位的话不需乘1000 + var Y = date.getFullYear() + var M = + date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var dateStr = '' + switch (type) { + case 1: + dateStr = Y + '年' + M + '月' + D + '日' + break + case 2: + dateStr = Y + '-' + M + '-' + D + break + default: + dateStr = Y + '年' + M + '月' + D + '日' + } + return dateStr +} + +// 标准化时间 即将 2015年12月31日 转为 2015-12-31 +const dateFormat = function (dateStr) { + if (dateStr) { + dateStr = dateStr.replace('年', '-') + dateStr = dateStr.replace('月', '-') + dateStr = dateStr.replace('日', '') + return dateStr + } else { + return '' + } +} + + +// 存储本地浏览器 mainKey是唯一标识 aryy是存储书 ,type - save 是存 getdata是取 +const saveactionDate = function (mainKey, arry, type) { + const _this = this + if (arry) { + arry.forEach(item => { + if (type === 'save') { + // 保存操作 + window.localStorage.setItem(mainKey + '-' + item, JSON.stringify(_this[item])) + } else { + // 回显 + _this[item] = JSON.parse(window.localStorage.getItem(mainKey + '-' + item) ? window.localStorage + .getItem(mainKey + '-' + item) : []) + } + }) + } +} + +export { + formatDate, + timestampToTime, + dateFormat, + saveactionDate +}; diff --git a/src/utils/validate.js b/src/utils/validate.js new file mode 100644 index 0000000..fff67aa --- /dev/null +++ b/src/utils/validate.js @@ -0,0 +1,158 @@ +/** + * 验证是否为空 + * @param string|array value + * @returns + */ +function isEmpty(value) { + if (value === null || value === undefined || value === "") { + return true; + } + if (Array.isArray(value) && value.length === 0) { + return true; + } + return false; +} + +/** + * 是否为手机号 + * @param {*} value + * @param {*} param1 + * @returns + */ +export const mobileValidator = (value, [locale]) => { + if (isEmpty(value)) { + return true; + } + if ((!locale || locale == "CN") && /^1[3456789][0-9]{9}$/.test(value)) { + return true; + } else if (locale == "EN" && /^\+[\d]{1,5}\s[\d]{1,14}$/.test(value)) { + return true; + } + return "请输入正确的手机号"; +}; + +/** + * 是否为电话号码 + * @param value + * @param {*} param1 + * @returns + */ +export const telValidator = (value, [locale]) => { + if (isEmpty(value)) { + return true; + } + if ((!locale || locale == "CN") && !/^(?:0|8)[1-9]{3,}-[0-9]{8}$/.test(value)) { + return true; + } + return "电话号码格式不正确"; +}; + +/** + * 身份证号码是否正确 + * @param string value + * @returns + */ +export const identifyValidator = (value) => { + if (isEmpty(value)) { + return true; + } + if (!/(?:^\d{15}$)|(?:^\d{18}$)|(?:^\d{17}(?:\d|X|x)$)/.test(value)) { + return "身份证号码格式不正确"; + } + return true; +}; + +/** + * 验证小数位长度 + * @param number value 值 + * @param number param1.length 为小数位长度 + * @returns + */ +export const decimalValidator = (value, [length]) => { + if (isEmpty(value)) { + return true; + } + let regExp = new RegExp("^[0-9]+(?:.[0-9])?$"); + if (length) { + regExp = new RegExp("^[0-9]+(?:.[0-9]{1," + length + "})?$"); + } + if (regExp.test(value)) { + return true; + } + return "小数位长度不正确"; +}; + +/** + * 验证是否为大写字母 + * @param string value + * @returns + */ +export const letterUpperValidator = (value) => { + if (isEmpty(value) || /^[A-Z]+$/.test(value)) { + return true; + } + return "请填写大写字母"; +}; + +/** + * 验证是否为小写字母 + * @param string value + * @returns + */ +export const letterLowerValidator = (value) => { + if (isEmpty(value) || /^[a-z]+$/.test(value)) { + return true; + } + return "请填写小写字母"; +}; + +/** + * 验证是否为中文 + * @param string value + * @returns + */ +export const characterCNValidator = (value) => { + if (isEmpty(value) || /^[\u4e00-\u9fa5]+$/.test(value)) { + return true; + } + return "请输入中文"; +}; + +/** + * 验证是否为邮编格式 + * @param string value + * @returns + */ +export const zipcodeValidator = (value) => { + if (isEmpty(value) || /^[1-9]\d{5}(?!\d)$/.test(value)) { + return true; + } + return "请输入正确邮编"; +}; + +/** + * 验证是否为传真格式 + * @param string value + * @returns + */ +export const faxValidator = (value) => { + if (isEmpty(value) || /^(\d{3,4}-)?\d{7,8}$/.test(value)) { + return true; + } + return "请输入正确的传真格式"; +}; + + +const allValid = { + mobile: mobileValidator, + tel: telValidator, + identify: identifyValidator, + decimal: decimalValidator, + letter_upper: letterUpperValidator, + letter_lower: letterLowerValidator, + character_cn: characterCNValidator, + zipcode: zipcodeValidator, + fax: faxValidator +}; + +export default allValid; diff --git a/src/views/PIP/copyright.vue b/src/views/PIP/copyright.vue new file mode 100644 index 0000000..58a0676 --- /dev/null +++ b/src/views/PIP/copyright.vue @@ -0,0 +1,335 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/copyright_add.vue b/src/views/PIP/copyright_add.vue new file mode 100644 index 0000000..fa9e6e5 --- /dev/null +++ b/src/views/PIP/copyright_add.vue @@ -0,0 +1,701 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/copyright_edit.vue b/src/views/PIP/copyright_edit.vue new file mode 100644 index 0000000..18443da --- /dev/null +++ b/src/views/PIP/copyright_edit.vue @@ -0,0 +1,745 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/patent.vue b/src/views/PIP/patent.vue new file mode 100644 index 0000000..4dc2c4f --- /dev/null +++ b/src/views/PIP/patent.vue @@ -0,0 +1,421 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/patent_add.vue b/src/views/PIP/patent_add.vue new file mode 100644 index 0000000..5ef64dd --- /dev/null +++ b/src/views/PIP/patent_add.vue @@ -0,0 +1,866 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/patent_edit.vue b/src/views/PIP/patent_edit.vue new file mode 100644 index 0000000..4d37a36 --- /dev/null +++ b/src/views/PIP/patent_edit.vue @@ -0,0 +1,906 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/quality_requisition.vue b/src/views/PIP/quality_requisition.vue new file mode 100644 index 0000000..a256234 --- /dev/null +++ b/src/views/PIP/quality_requisition.vue @@ -0,0 +1,379 @@ + + + + \ No newline at end of file diff --git a/src/views/PIP/trademark.vue b/src/views/PIP/trademark.vue new file mode 100644 index 0000000..068a5da --- /dev/null +++ b/src/views/PIP/trademark.vue @@ -0,0 +1,408 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/trademark_add.vue b/src/views/PIP/trademark_add.vue new file mode 100644 index 0000000..6148130 --- /dev/null +++ b/src/views/PIP/trademark_add.vue @@ -0,0 +1,747 @@ + + + + + \ No newline at end of file diff --git a/src/views/PIP/trademark_edit.vue b/src/views/PIP/trademark_edit.vue new file mode 100644 index 0000000..17f223e --- /dev/null +++ b/src/views/PIP/trademark_edit.vue @@ -0,0 +1,771 @@ + + + + + \ No newline at end of file diff --git a/src/views/agv/agv.scss b/src/views/agv/agv.scss new file mode 100644 index 0000000..459c1dd --- /dev/null +++ b/src/views/agv/agv.scss @@ -0,0 +1,67 @@ +/**/ +.m-16{margin: 16px;} +.p-16{padding: 16px;} +.m-t-16{ margin-top: 16px;} +.m-b-16{ margin-bottom: 16px;} +.m-l-16{ margin-left: 16px;} +.m-r-16{ margin-right: 16px;} +.m-r-8{ margin-right: 8px;} + +/**/ +.isGreen{ + color: #00AA5B +} +.isYellow{ + color: #FFAB00 +} +.isBlue{ + color: #4178D5 +} +.isCyan{ + color: #3CBFFF +} +.isRed{ + color: #FF4747 +} +.Cursor{ + cursor: pointer; +} +.el-button+.el-button{ + margin-left: 0!important; +} +.el-form-item{ + margin-bottom: 10px!important; +} +:deep(.el-dialog__title){ + font-weight: bold; + color: #303133; +} +:deep(.el-dialog__body){ + padding-top: 16px!important; + padding-bottom: 16px!important; +} +.pagination { + text-align: right; +} + +/**顶部图标**/ +.Files{ + background: url('../../assets/daochu.png'); + width: 14px; + height: 14px; + display: block; + background-size: cover; + float: left; + margin-right: 5px; +} +::v-deep(.top-box .el-button--small) { + font-size: 14px; +} +::v-deep(.top-box .el-button:hover,.top-box .el-button:focus){ + background: #ecf5ff; + color: #409eff; +} +::v-deep(.top-box .el-button--primary:focus, .top-box .el-button--primary:hover){ + background: #ecf5ff; + color: #409eff; +} \ No newline at end of file diff --git a/src/views/agv/agvList.vue b/src/views/agv/agvList.vue new file mode 100644 index 0000000..7980b3f --- /dev/null +++ b/src/views/agv/agvList.vue @@ -0,0 +1,373 @@ + + + + + \ No newline at end of file diff --git a/src/views/agv/hopperCar.vue b/src/views/agv/hopperCar.vue new file mode 100644 index 0000000..7a1d0c2 --- /dev/null +++ b/src/views/agv/hopperCar.vue @@ -0,0 +1,383 @@ + + + + + \ No newline at end of file diff --git a/src/views/agv/position.vue b/src/views/agv/position.vue new file mode 100644 index 0000000..9b04690 --- /dev/null +++ b/src/views/agv/position.vue @@ -0,0 +1,541 @@ + + + + + \ No newline at end of file diff --git a/src/views/agv/stockDetail.vue b/src/views/agv/stockDetail.vue new file mode 100644 index 0000000..edb1d3e --- /dev/null +++ b/src/views/agv/stockDetail.vue @@ -0,0 +1,206 @@ + + + + + \ No newline at end of file diff --git a/src/views/agv/taskList.vue b/src/views/agv/taskList.vue new file mode 100644 index 0000000..8e6b5cd --- /dev/null +++ b/src/views/agv/taskList.vue @@ -0,0 +1,312 @@ + + + + + \ No newline at end of file diff --git a/src/views/gys/quality_requisition.vue b/src/views/gys/quality_requisition.vue new file mode 100644 index 0000000..2fcce95 --- /dev/null +++ b/src/views/gys/quality_requisition.vue @@ -0,0 +1,483 @@ + + + + \ No newline at end of file diff --git a/src/views/index/index.vue b/src/views/index/index.vue new file mode 100644 index 0000000..abc969f --- /dev/null +++ b/src/views/index/index.vue @@ -0,0 +1,850 @@ + + + + diff --git a/src/views/index/log.vue b/src/views/index/log.vue new file mode 100644 index 0000000..fe6ecda --- /dev/null +++ b/src/views/index/log.vue @@ -0,0 +1,278 @@ + + + + diff --git a/src/views/index/oindex.vue b/src/views/index/oindex.vue new file mode 100644 index 0000000..920d571 --- /dev/null +++ b/src/views/index/oindex.vue @@ -0,0 +1,768 @@ + + + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..c47f29c --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,50 @@ + + diff --git a/src/views/mold/add_mold.vue b/src/views/mold/add_mold.vue new file mode 100644 index 0000000..0581560 --- /dev/null +++ b/src/views/mold/add_mold.vue @@ -0,0 +1,859 @@ + + + + diff --git a/src/views/mold/edit_mold.vue b/src/views/mold/edit_mold.vue new file mode 100644 index 0000000..1f331b6 --- /dev/null +++ b/src/views/mold/edit_mold.vue @@ -0,0 +1,701 @@ + + + + diff --git a/src/views/mold/index.vue b/src/views/mold/index.vue new file mode 100644 index 0000000..be7b539 --- /dev/null +++ b/src/views/mold/index.vue @@ -0,0 +1,414 @@ + + + + diff --git a/src/views/public/404.vue b/src/views/public/404.vue new file mode 100644 index 0000000..a0175bf --- /dev/null +++ b/src/views/public/404.vue @@ -0,0 +1,12 @@ + + + diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..2419ca3 --- /dev/null +++ b/vue.config.js @@ -0,0 +1,83 @@ +const settings = require('./src/settings') +const path = require('path') +const fs = require('fs') // 新增:用于HTTPS证书 + +function resolve(dir) { + return path.join(__dirname, dir) +} + +// 自动生成自签名证书(开发环境使用) +function getHttpsConfig() { + try { + return { + https: { + key: fs.readFileSync('./cert/key.pem'), + cert: fs.readFileSync('./cert/cert.pem') + } + } + } catch (e) { + console.warn('未找到HTTPS证书,将使用HTTP协议') + return { https: false } + } +} + +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') +const Timestamp = new Date().getTime() +const CompressionWebpackPlugin = require('compression-webpack-plugin') +const productionGzipExtensions = ['js', 'css'] +const Components = require('unplugin-vue-components/webpack') +const { ElementPlusResolver } = require('unplugin-vue-components/resolvers') + +module.exports = { + assetsDir: 'static', + runtimeCompiler: true, + css: { + requireModuleExtension: true, + sourceMap: process.env.NODE_ENV == 'development' + }, + + configureWebpack: { + name: settings.title, + resolve: { + extensions: ['.js', '.vue', '.json'], + alias: { + '@': resolve('src') + } + }, + plugins: [ + new UglifyJsPlugin({ + uglifyOptions: { + compress: { + drop_console: process.env.NODE_ENV === 'production' // 只在生产环境移除console + } + } + }), + new CompressionWebpackPlugin({ + filename: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), + threshold: 10240, + minRatio: 0.8, + deleteOriginalAssets: false + }), + Components({ + resolvers: [ElementPlusResolver()] + }) + ], + output: { + filename: `js/[name].${Timestamp}.js`, + chunkFilename: `js/[name].${Timestamp}.js` + } + }, + + devServer: { + host: 'swt.costapi.f2b211.com', + port: 8080, + ...getHttpsConfig(), // 动态获取HTTPS配置 + hot: true, + open: true, + headers: { + 'Access-Control-Allow-Origin': '*' // 解决跨域问题 + } + } +} \ No newline at end of file