diff --git a/package.json b/package.json index 5b95413..5ebd61f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "axios": "^1.6.1", "core-js": "^3.25.3", "echarts": "^5.4.3", + "loglevel": "^1.9.1", "nuxt": "^2.15.8", "vue": "^2.7.10", "vue-echarts": "^4.1.0", diff --git a/pages/index.vue b/pages/index.vue index 4347686..4df2f89 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,656 +1,7 @@ - - - - {{ date }} - - - {{ time }} - - - {{ title }} - - - - - 教育资源 - - - - - - - - - - - - - - - - - - - - - - - - - - - 各年级学生分布 - - - - - - - - 性别分布 - - - - - - 男 - - {{ studentSexStatistics.men.total }} - - {{ studentSexStatistics.men.percent }}% - - - - - - - - - 女 - - {{ studentSexStatistics.women.total }} - - {{ studentSexStatistics.women.percent }}% - - - - - - - - - - 电子班牌概况 - - - - - - - - - 今日门禁进出 - - - - - - - - - - - - - - - - - - - - - 今日在校人数 - - - - - - 考勤统计 - - - - - - - - - - - - - 各年级数人分布 - - - 一年级 - 二年级 - 三年级 - 四年级 - 五年级 - 六年级 - - - - - - - - 近7日请假统计 - - - - - - - - - - 电子班牌使用情况 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 签到记录 - - - - - - - - - - - + hello world - - diff --git a/pages/visual/PeakCoalView.vue b/pages/visual/PeakCoalView.vue index 7aaa5de..fda5156 100644 --- a/pages/visual/PeakCoalView.vue +++ b/pages/visual/PeakCoalView.vue @@ -73,6 +73,11 @@ export default { } }, mounted() { + this.$log.info('test') + this.$log.error('test') + this.$log.trace('test') + this.$log.info('test') + this.$log.warn('test') this.getAutoStyle() window.addEventListener('resize', (e) => { e.stopPropagation() @@ -161,11 +166,13 @@ body, html, #__nuxt, #__layout { height: 2721px; background: url("assets/peakCoalImages/left/left-content-bg.png") no-repeat center 60px; padding: 300px 476px 100px 412px; + perspective: 500px; .left-data-panel { height: 100%; width: 100%; - transform: rotate(2deg); + transform: rotateY(2deg); + transform-origin: left center; } } @@ -177,11 +184,13 @@ body, html, #__nuxt, #__layout { height: 2721px; background: url("assets/peakCoalImages/right/right-content-bg.png") no-repeat center 60px; padding: 300px 476px 100px 480px; + perspective: 500px; .right-data-panel { height: 100%; width: 100%; - transform: rotate(-2deg); + transform: rotateY(-2deg); + transform-origin: right center; } } diff --git a/plugins/antd-ui.js b/plugins/antd-ui.js index 70d7ae2..2e502f6 100644 --- a/plugins/antd-ui.js +++ b/plugins/antd-ui.js @@ -1,5 +1,11 @@ import Vue from 'vue' import Antd from 'ant-design-vue/lib' +import log from 'loglevel'; +const logLevel = parseInt(localStorage.getItem('loglevel') || '-1') +if (logLevel >= 0) { + log.setDefaultLevel(logLevel) +} +Vue.prototype.$log = log Vue.use(Antd) diff --git a/services/apis.js b/services/apis.js index 18afbba..302b900 100644 --- a/services/apis.js +++ b/services/apis.js @@ -1,54 +1,10 @@ -import { get, post } from './axios' -// 教育资源统计信息 -export const getResourcesStatistics = function () { - return get('/cloudStatistics/getResourcesStatistics') +import { SupportLog, Request, Get, Post, Delete, Log } from './httpModule' +@SupportLog +@Request('/aaa') +class Apis { + @Log('test') + @Get('test') + test () {} } -// 各年级学生分布信息 -export const getStudentGradStatistics = function () { - return get('/cloudStatistics/getStudentGradStatistics') -} - -// 性别分布 -export const getStudentSexStatistics = function () { - return get('/cloudStatistics/getStudentSexStatistics') -} - -// 设备概况 -export const getDeviceStatistics = function () { - return get('/cloudStatistics/getDeviceStatistics') -} - -// 门禁进出人数 -export const getAccessStatistics = function () { - return get('/cloudStatistics/getAccessStatistics') -} - -// 查询班牌统计信息 -export const getClassBrandStatistics = function () { - return get('/cloudStatistics/getClassBrandStatistics') -} - - -// 签到记录-最新10条 -export const getAccessInfo = function () { - return get('/cloudStatistics/getAccessInfo') -} - -// 在校人数与考勤统计 -export const getStudentAttendanceStatistics = function () { - return get('/cloudStatistics/getStudentAttendanceStatistics') -} - - -// 各年级人数及出勤分布 -export const getGradeStatistics = function () { - return get('/cloudStatistics/getGradeStatistics') -} - -// 近7日请假统计 -export const getLeaveStatistics = function () { - return get('/cloudStatistics/getLeaveStatistics') -} - - +export default new Apis() diff --git a/services/axios.js b/services/httpModule/axios.js similarity index 81% rename from services/axios.js rename to services/httpModule/axios.js index 230f182..7125c9d 100644 --- a/services/axios.js +++ b/services/httpModule/axios.js @@ -41,14 +41,7 @@ service.interceptors.response.use( } ) -const get = (url, params = {}, config) => { - return service.get(url, { params: params || {}, ...(config ||{}) }) -} -const post = (url, params = {}, config) => { - return service.post(url, params, config || {}) -} - -export { get, post } +export default service diff --git a/services/httpModule/http.js b/services/httpModule/http.js new file mode 100644 index 0000000..5f38244 --- /dev/null +++ b/services/httpModule/http.js @@ -0,0 +1,175 @@ + +import checkTypes from "@/utils/checkTypes"; +import {urlJoin} from "@/utils/url"; +import {isEmpty} from "@/utils/tools"; +import http from './axios' +const isDev = process.env.NODE_ENV === 'development' + +const getUrlByCode = function (url, obj = {}) { + const reg = /\{([a-zA-Z0-9]+)\}/g + const match = url.match(reg) || [] + let str = url + match.forEach(field => { + const _field = field.substr(1, field.length - 2) + str = str.replace(field, obj[_field] || field) + }) + return str +} + +/** + * 获取请求地址 + * @param prefix + * @param url + */ +const getUrl = (prefix, url) => { + if (url.startsWith('http') || !prefix) return url + if (prefix.startsWith('http')) { + return urlJoin(`${prefix}/${url}`) + } + return urlJoin(`/${prefix}/${url}`) +} + +/** + * 请求处理 + * @param httpMethod { String } 请求类型 + * @param fetchUrl { String } 请求地址 + * @returns {function(...[*]=)} + */ +const decoratorHandler = function (httpMethod, fetchUrl) { + return function (target, propertyKey, descriptor) { + const method = descriptor.value + descriptor.value = async function (params = {}, options = {}, $logName) { + const res = await method.apply(this, [params, options]) + let _params = params + let _options = options + if (!isEmpty(res) && checkTypes.isObject(res) && res.params) { + _params = res.params + _options = res.options + } + if (this._commonAxiosOptions) { + _options = { + ...this._commonAxiosOptions, + ..._options + } + } + // 生成请求参数 + const httpOptions = { + method: httpMethod, + url: getUrl(this.prefix, getUrlByCode(fetchUrl || propertyKey, _params)), + baseURL: '', + ..._options + } + if (httpMethod === 'get') { + httpOptions.params = _params + } else { + httpOptions.data = _params + } + try { + const response = await http(httpOptions) + if (this.log) return this.log($logName, httpOptions, response) + return response + } catch (error) { + if (this.log) throw this.log($logName, httpOptions, null, error) + throw error + } + } + } +} + +/** + * 根据环境过滤地址 + * @param devUrl { String } + * @param prodUrl { String } + * @returns {String} + */ +const filterUrl = function (devUrl, prodUrl) { + return (isDev ? devUrl : prodUrl || devUrl) || undefined +} + +/** + * 初始化请求接口前缀 + * @param devPrefix { String } 开发环境请求地址前缀 + * @param prodPrefix { String } 生产环境请求地址前缀 + * @returns {function(*): {new(): {prefix: *}, prototype: {prefix: *}}} + * @constructor + */ +export const Request = (devPrefix, prodPrefix) => { + return function (constructor) { + return class extends constructor { + prefix = isDev ? devPrefix : prodPrefix || devPrefix + } + } +} + +/** + * Post请求 + * @param devUrl { String } + * @param prodUrl { String } + * @returns {function(...[*]=)} + * @constructor + */ +export const Post = (devUrl, prodUrl) => { + let _devUrl = typeof devUrl === 'string' ? devUrl : prodUrl + return decoratorHandler.call(this, 'post', filterUrl(_devUrl, prodUrl)) +} + +/** + * Get请求 + * @param devUrl { String } + * @param prodUrl { String } + * @returns {function(...[*]=)} + * @constructor + */ +export const Get = (devUrl, prodUrl) => { + let _devUrl = typeof devUrl === 'string' ? devUrl : prodUrl + return decoratorHandler.call(this, 'get', filterUrl(_devUrl, prodUrl)) +} + +/** + * Put请求注解 + * @param devUrl { String } + * @param prodUrl { String } + * @returns {function(...[*]=)} + * @constructor + */ +export const Put = (devUrl, prodUrl) => { + let _devUrl = typeof devUrl === 'string' ? devUrl : prodUrl + return decoratorHandler.call(this, 'put', filterUrl(_devUrl, prodUrl)) +} + +/** + * Delete请求注解 + * @param devUrl { String } + * @param prodUrl { String } + * @returns {function(...[*]=)} + * @constructor + */ +export const Delete = (devUrl, prodUrl) => { + let _devUrl = typeof devUrl === 'string' ? devUrl : prodUrl + return decoratorHandler.call(this, 'delete', filterUrl(_devUrl, prodUrl)) +} + +/** + * 为当前方法添加其他内容 + * @param _options + * @return {function(...[*]=)} + * @constructor + */ +export const AxiosOptions = function (_options = {}) { + return function (target, propertyKey, descriptor) { + // 类 + if (target.prototype) { + return class extends target { + _commonAxiosOptions = _options || {} + } + } else { + const method = descriptor.value + descriptor.value = function (params = {}, options = {}, $logName) { + return method.apply(this, [params, { + ...options, + ..._options + }, $logName]) + } + } + } +} diff --git a/services/httpModule/httpLog.js b/services/httpModule/httpLog.js new file mode 100644 index 0000000..3bb650e --- /dev/null +++ b/services/httpModule/httpLog.js @@ -0,0 +1,50 @@ +import moment from "moment"; +const isDev = process.env.NODE_ENV === 'development' +// 打印日志 +export const httpLog = function (logName, { method, url, params, data }, response, error) { + const logColor = response ? '#0ad554' : '#FF0000' + const logLevel = parseInt(localStorage.getItem('loglevel') || '-1') + if (logLevel >= 0) { + const time = moment().format('YYYY-MM-DD HH:mm:ss') + console.groupCollapsed(`%c [DEBUG] HTTP: ${time}-${method.toUpperCase()}@${url}@${logName || ''}`, + `color: #fff;background-color: ${logColor};` + ) + if (data) { + console.log(' query: %o', data) + } + if (params) { + console.log(' query: %o', params) + } + if (response) { + console.log('response: %o', response) + } + if (error) { + console.log(' error: %o', error) + } + console.groupEnd() + } + if (error) throw error + if (response) return response +} + +/** + * 为服务支持日志功能 + * @param constructor + * @return {{new(): {httpLog: *}, prototype: {httpLog: *}}} + * @constructor + */ +export const SupportLog = function (constructor) { + return class extends constructor { + log = httpLog + } +} + + +export const Log = function (logName) { + return function (target, propertyKey, descriptor) { + const method = descriptor.value || target[propertyKey] + descriptor.value = function (params = {}, options = {}) { + return method && method.apply(this, [params, options, logName]) + } + } +} diff --git a/services/httpModule/index.js b/services/httpModule/index.js new file mode 100644 index 0000000..9f24e60 --- /dev/null +++ b/services/httpModule/index.js @@ -0,0 +1,2 @@ +export * from './http' +export * from './httpLog' diff --git a/utils/checkTypes.js b/utils/checkTypes.js new file mode 100644 index 0000000..2d565ba --- /dev/null +++ b/utils/checkTypes.js @@ -0,0 +1,8 @@ +const checkTypes = {}; +['Array', 'Object', 'String', 'Number', 'Boolean', 'Null', 'Undefined', 'RegExp', 'Function'].forEach(type => { + checkTypes[`is${type}`] = function (data) { + return Object.prototype.toString.call(data) === `[object ${type}]` + } +}) + +export default checkTypes diff --git a/utils/query.js b/utils/query.js new file mode 100644 index 0000000..50d6928 --- /dev/null +++ b/utils/query.js @@ -0,0 +1,27 @@ +/** + * @function 将url中的查询字符串转换为obj + * @param search { String } 查询字符串,默认为当前url中的查询字符串 + * @return { Object } + */ +export const query2object = (search = location.search.substr(1)) => { + if (!search.length) return {} + const query = {} + for (const queryString of search.split('&')) { + const kv = queryString.split('=') + query[kv[0]] = kv[1] + } + return query +} + +/** + * @function 将对象转换为查询字符串,不带#和? + * @param object + */ +export const object2query = (object) => { + if (Object.keys(object).length === 0) return '' + let search = '' + for (const key in object) { + search += `${key}=${object[key]}&` + } + return search.substr(0, search.length - 1) +} diff --git a/utils/screen.js b/utils/screen.js new file mode 100644 index 0000000..2a44abe --- /dev/null +++ b/utils/screen.js @@ -0,0 +1,49 @@ +/** + * @function 开启全屏方法 + * @param el { Element } 全屏的dom,默认是document.documentElement + */ +function fullScreen(el = document.documentElement) { + let rfs = el.requestFullscreen + || el.webkitRequestFullScreen + || el.mozRequestFullScreen + || el.msRequestFullscreen + try { + if (typeof rfs !== 'undefined' && rfs) rfs.call(el) + } catch (e) { + console.error('fullScreen: %o', e); + } +} + +/** + * @function 监测是否全屏 + * @return {boolean} + */ +function isFullscreen() { + return !!( + document.webkitIsFullScreen || + document.mozFullScreen || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement || + document.fullscreenElement || + window.fullScreen + ) +} + +/** + * @function 退出全屏 + */ +function exitScreen() { + const exitFn = document.exitFullscreen || document.mozCancelFullScreen || document.webkitCancelFullScreen || document.msExitFullscreen + try { + if (typeof exitFn !== 'undefined' && exitFn) exitFn.call(document) + } catch (e) { + console.error('exitScreen: %o', e) + } +} + +export { + fullScreen, + exitScreen, + isFullscreen +} diff --git a/utils/timer.js b/utils/timer.js new file mode 100644 index 0000000..89db381 --- /dev/null +++ b/utils/timer.js @@ -0,0 +1,81 @@ +/* + 开发环境不开启定时器 + */ +const isStartTimer = process.env.NODE_ENV !== 'development' + +/** + * @function 定时器 + * @param fn { function } 执行过程 + * @param duration { timestamp } 间隔时间 + * @param immediate { boolean } 是否立即执行 + * @returns {{stop(): void, restart: restart}} + */ +export default function timer(fn, duration = 5 * 1000, immediate = true) { + // 执行队列 + let processes = [] + let _args = [] + // 定义定时器 + let timer = null + let _duration = duration + // 过程支持list和单一函数 + if (Array.isArray(fn)) { + processes.push.apply(processes, fn) + } else { + processes.push(fn) + } + /** + * @function 过程执行器 + * @param processes { Array | function } + * @param args { any } + * @returns {Promise} + */ + const exec = async (processes, ...args) => { + _args = args + try { + if (timer) { + clearTimeout(timer) + timer = null + } + for (let process of processes) { + if (typeof process === 'function') { + await process(..._args) + } + } + } catch (e) { + console.error('TimerError:', e) + } finally { + if (!isStartTimer) return false + timer = setTimeout(() => { + exec(processes, ..._args) + }, _duration) + } + } + if (immediate) { + exec(processes) + } + return { + stop () { + if (timer) { + clearTimeout(timer) + timer = null + processes = null + _args = null + } + }, + clearParams () { + _args = [] + }, + /** + * 定时器重启 + * @param args { { params: Array, duration: number } } 回调的参数 + */ + restart: function (options = {}) { + const { params = [], duration } = options + // 不能用全等,对象中的undefined 与 undefined 不是全等 + if (duration != undefined || duration != null) { + _duration = duration + } + exec(processes, ...(params || [])) + } + } +} diff --git a/utils/tools.js b/utils/tools.js index e69de29..6789706 100644 --- a/utils/tools.js +++ b/utils/tools.js @@ -0,0 +1,71 @@ +import checkTypes from "@/utils/checkTypes"; +export function guid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} + +export const getTheTruth = function (data) { + if (typeof data !== 'object' && typeof data !== 'string') return data + if (typeof data === 'string') { + if (data.startsWith('{') || data.startsWith('[')) + return getTheTruth(JSON.parse(data)) + return data + } + if (Array.isArray(data)) return data.map((item) => getTheTruth(item)) + for (const key in data) data[key] = getTheTruth(data[key]) + return data +} + + +export const isEmpty = (data) => { + return checkTypes.isUndefined(data) || checkTypes.isNull(data) || data == NaN +} + + +export function isDef(v) { + return v !== undefined && v !== null +} + +export function isPromise(val) { + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ) +} + +export const toPromise = (task, data) => { + return new Promise((resolve, reject) => { + let _isPromise = false // 当前任务是不是一个promise + try { + // 执行任务 + const results = task(data) + // 任务是一个异步函数,执行promise + if (isPromise(results)) { + _isPromise = true + results.then(resolve, reject) + } else { + // 非异步任务 + resolve(results) + } + } catch (e) { + if (!_isPromise) { + reject(e) + } + } + }) +} + +export function getSize(size, defaultValue = undefined) { + if (!size) { + if (checkTypes.isNumber(size)) return size + return defaultValue || undefined + } + if (/^\-?\d+$/.test(`${size}`)) return `${size}px` + if (/(%|px)$/.test(`${size}`)) return size + if (/calc/.test(`${size}`)) return size + return size +} diff --git a/utils/url.js b/utils/url.js new file mode 100644 index 0000000..3f9a8ec --- /dev/null +++ b/utils/url.js @@ -0,0 +1,7 @@ +export function urlJoin() { + return [].slice + .call(arguments) + .join('/') + .replace(/\/+/g, '/') + .replace(':/', '://') +} diff --git a/yarn.lock b/yarn.lock index 89d3f15..e5087d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5337,6 +5337,11 @@ lodash@^4.15.0, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17. resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +loglevel@^1.9.1: + version "1.9.1" + resolved "https://registry.npmmirror.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7" + integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg== + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"