diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..fa10d7c --- /dev/null +++ b/.env.development @@ -0,0 +1,9 @@ +ENV = 'development' + +# 接口地址 +# 许镇-本地服地址 +VUE_APP_BASE_API = 'http://192.168.99.63:8080' +VUE_APP_WS_API = 'ws://192.168.99.63:8081' + +# 是否启用 babel-plugin-dynamic-import-node插件 +VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..9cb3545 --- /dev/null +++ b/.env.production @@ -0,0 +1,8 @@ +ENV = 'production' + +# 如果使用 Nginx 代理后端接口,那么此处需要改为 '/',文件查看 Docker 部署篇,Nginx 配置 +# 接口地址,注意协议,如果你没有配置 ssl,需要将 https 改为 http +VUE_APP_BASE_API = 'http://172.22.0.23:8080' +# VUE_APP_BASE_API = 'http://127.0.0.1:8080' +# 如果接口是 http 形式, wss 需要改为 ws +VUE_APP_WS_API = 'ws://172.22.0.23:7071' diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..0c290b7 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,197 @@ +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint', + sourceType: 'module' + }, + env: { + browser: true, + node: true, + es6: true, + }, + extends: ['plugin:vue/recommended', 'eslint:recommended'], + + + rules: { + "vue/max-attributes-per-line": [2, { + "singleline": 10, + "multiline": { + "max": 1, + "allowFirstLine": false + } + }], + "vue/singleline-html-element-content-newline": "off", + "vue/multiline-html-element-content-newline":"off", + "vue/name-property-casing": ["error", "PascalCase"], + "vue/no-v-html": "off", + 'accessor-pairs': 2, + 'arrow-spacing': [2, { + 'before': true, + 'after': true + }], + 'block-spacing': [2, 'always'], + 'brace-style': [2, '1tbs', { + 'allowSingleLine': true + }], + 'camelcase': [0, { + 'properties': 'always' + }], + 'comma-dangle': [2, 'never'], + 'comma-spacing': [2, { + 'before': false, + 'after': true + }], + 'comma-style': [2, 'last'], + 'constructor-super': 2, + 'curly': [2, 'multi-line'], + 'dot-location': [2, 'property'], + 'eol-last': 2, + 'eqeqeq': ["error", "always", {"null": "ignore"}], + 'generator-star-spacing': [2, { + 'before': true, + 'after': true + }], + 'handle-callback-err': [2, '^(err|error)$'], + 'indent': [2, 2, { + 'SwitchCase': 1 + }], + 'jsx-quotes': [2, 'prefer-single'], + 'key-spacing': [2, { + 'beforeColon': false, + 'afterColon': true + }], + 'keyword-spacing': [2, { + 'before': true, + 'after': true + }], + 'new-cap': [2, { + 'newIsCap': true, + 'capIsNew': false + }], + 'new-parens': 2, + 'no-array-constructor': 2, + 'no-caller': 2, + 'no-console': 'off', + 'no-class-assign': 2, + 'no-cond-assign': 2, + 'no-const-assign': 2, + 'no-control-regex': 0, + 'no-delete-var': 2, + 'no-dupe-args': 2, + 'no-dupe-class-members': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty-character-class': 2, + 'no-empty-pattern': 2, + 'no-eval': 2, + 'no-ex-assign': 2, + 'no-extend-native': 2, + 'no-extra-bind': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-parens': [2, 'functions'], + 'no-fallthrough': 2, + 'no-floating-decimal': 2, + 'no-func-assign': 2, + 'no-implied-eval': 2, + 'no-inner-declarations': [2, 'functions'], + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-iterator': 2, + 'no-label-var': 2, + 'no-labels': [2, { + 'allowLoop': false, + 'allowSwitch': false + }], + 'no-lone-blocks': 2, + 'no-mixed-spaces-and-tabs': 2, + 'no-multi-spaces': 2, + 'no-multi-str': 2, + 'no-multiple-empty-lines': [2, { + 'max': 1 + }], + 'no-native-reassign': 2, + 'no-negated-in-lhs': 2, + 'no-new-object': 2, + 'no-new-require': 2, + 'no-new-symbol': 2, + 'no-new-wrappers': 2, + 'no-obj-calls': 2, + 'no-octal': 2, + 'no-octal-escape': 2, + 'no-path-concat': 2, + 'no-proto': 2, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-return-assign': [2, 'except-parens'], + 'no-self-assign': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-shadow-restricted-names': 2, + 'no-spaced-func': 2, + 'no-sparse-arrays': 2, + 'no-this-before-super': 2, + 'no-throw-literal': 2, + 'no-trailing-spaces': 2, + 'no-undef': 2, + 'no-undef-init': 2, + 'no-unexpected-multiline': 2, + 'no-unmodified-loop-condition': 2, + 'no-unneeded-ternary': [2, { + 'defaultAssignment': false + }], + 'no-unreachable': 2, + 'no-unsafe-finally': 2, + 'no-unused-vars': [2, { + 'vars': 'all', + 'args': 'none' + }], + 'no-useless-call': 2, + 'no-useless-computed-key': 2, + 'no-useless-constructor': 2, + 'no-useless-escape': 0, + 'no-whitespace-before-property': 2, + 'no-with': 2, + 'one-var': [2, { + 'initialized': 'never' + }], + 'operator-linebreak': [2, 'after', { + 'overrides': { + '?': 'before', + ':': 'before' + } + }], + 'padded-blocks': [2, 'never'], + 'quotes': [2, 'single', { + 'avoidEscape': true, + 'allowTemplateLiterals': true + }], + 'semi': [2, 'never'], + 'semi-spacing': [2, { + 'before': false, + 'after': true + }], + 'space-before-blocks': [2, 'always'], + 'space-before-function-paren': [2, 'never'], + 'space-in-parens': [2, 'never'], + 'space-infix-ops': 2, + 'space-unary-ops': [2, { + 'words': true, + 'nonwords': false + }], + 'spaced-comment': [2, 'always', { + 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] + }], + 'template-curly-spacing': [2, 'never'], + 'use-isnan': 2, + 'valid-typeof': 2, + 'wrap-iife': [2, 'any'], + 'yield-star-spacing': [2, 'both'], + 'yoda': [2, 'never'], + 'prefer-const': 2, + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, + 'object-curly-spacing': [2, 'always', { + objectsInObjects: false + }], + 'array-bracket-spacing': [2, 'never'] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef4e647 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules +dist/ + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +package-lock.json \ No newline at end of file diff --git a/.trae/documents/traffic-flow-screen-plan.md b/.trae/documents/traffic-flow-screen-plan.md new file mode 100644 index 0000000..b770258 --- /dev/null +++ b/.trae/documents/traffic-flow-screen-plan.md @@ -0,0 +1,120 @@ +# 客流量大屏改造方案 + +## Context(背景与目标) + +现有项目 `hs-flow‑screen` 是一个深色科技风的"借阅/通知大屏"(三栏布局:总借阅、借阅排行、进馆、阅读之星、通知、新书推荐)。用户希望仿照目标页面 `http://libraryu.seaeverit.com:15000/main?token=...` 做一个**客流量大屏**,目标页面布局为:顶部 Header → "客流量展示"标签 → 三列卡片(当日进馆折线图 / 当月进馆折线图 / 当年进馆数字翻牌)→ 全宽"累计客流量汇总"翻牌器。 + +经确认的改造口径: +1. **保留深色科技风视觉**(深蓝背景 `#010326`、白色文字、蓝色发光 `#1AC9FF`、`common-title`/`module-content` 蓝色样式),**不**改成目标页的白底橙色,只仿照**布局结构**。 +2. **保留现有 Header 组件**(动画线条 + 天气 iframe + 渐变标题),标题用"黄石图书馆"。 +3. **删除不再使用的旧组件目录**。 +4. **数据先用 mock**,预留接口对接位置。 + +## 关键约束(基于源码确认) + +- 适配:rem 方案(`src/common/flexible.js`,1920 设计稿,1rem≈80px,范围 1366–2560),所有尺寸用 rem。 +- echarts 4:`this.$echarts` 全局已注册(`main.js`),用 `this.$echarts.init(this.$refs.chart)` + ref,**不要用 `getElementById`**(旧 `components/echart/*` 用 id 会有冲突)。 +- `resizeMixins.js`(`src/utils/resizeMixins.js`)提供自动 resize(要求组件 `data.chart` 字段),但其 `removeEventListener('reisze'…)` 拼写错误且不 dispose → 新组件须在 `beforeDestroy` 手动 `this.chart.dispose(); this.chart=null`。 +- 数字翻牌:`vue-count-to` 的 `CountTo`,用法 ``。 +- 滚动:`vue-seamless-scroll` 已 `Vue.use`,组件名 ``。 +- `.common-title` / `.module-content` 是 `.screen-main` 的后代选择器 → 新布局根容器必须仍是 `.screen-main`(叠加 `.traffic-main`)。 +- Header 标题 prop:`headerTitle`,`getCurrentTime()` 返回"YYYY年MM月DD日 HH:mm:ss"。 +- 端口 8080,`npm run serve` 自动开浏览器。 + +## 新文件结构 + +``` +src/views/ +├── index.vue # 改写:Header + ,删除旧的高度计算逻辑 +├── header/index.vue # 保留不动 +└── trafficFlow/ + ├── index.vue # 新建:.screen-main.traffic-main 布局容器 + └── components/ + ├── DayInFlowChart.vue # 当日进馆折线图(按小时) + ├── MonthInFlowChart.vue # 当月进馆折线图(按日) + ├── YearInFlowCard.vue # 当年进馆数字翻牌(单个大数字) + └── TotalFlowSummary.vue # 累计客流量汇总(8 位翻牌卡片,横向可滚动) +``` + +## 实施步骤 + +### 1. 新建 trafficFlow 4 个子组件 + index.vue(先建骨架,不破坏现有) +- `DayInFlowChart.vue` / `MonthInFlowChart.vue`:`
`,`mixins:[resize]`,`data.chart=null` + mock 数据,`mounted` → `initChart()` → `setOption()`,`beforeDestroy` dispose。setOption 用蓝色系(线 `#1AC9FF`、轴 `#2C75E0`、分割线 `rgba(44,117,224,0.3)` 虚线、白色文字、`areaStyle` 用 `this.$echarts.graphic.LinearGradient` 蓝色渐变、`smooth:true`)。 +- `YearInFlowCard.vue`:``,mock `yearNum:1286500`。 +- `TotalFlowSummary.vue`:`totalNum:98765432` → `computed.digitList` 用 `padStart(8,'0').split('')` 固定 8 位,每位一个 ``,外裹 `` 横向滚动(`direction:2`)。预留 `fetchData()` 注释。 +- `trafficFlow/index.vue`:模板见下方。 + +### 2. 在 `src/assets/styles/index.scss` 末尾追加客流量布局样式(不动现有任何规则) +新增 `// ===== 客流量大屏布局 =====` 段,关键规则: +- `.traffic-main { flex-direction:column; justify-content:flex-start; gap:0.125rem; }`(叠加在 `.screen-main` 上) +- `.traffic-section-title`(顶部"客流量展示"标签,用 `title-big.png`) +- `.traffic-row { display:flex; }`、`.traffic-row-3 { flex:1; gap:0.25rem; min-height:0; }`(min-height:0 防 echarts 高度坍缩) +- `.traffic-card { flex:1; display:flex; flex-direction:column; min-width:0; .module-content{flex:1; overflow:hidden; padding:0.125rem 0.2rem;} }` +- `.traffic-summary { height:1.6rem; }` +- `.chart-box { width:100%; height:100%; }` +- `.year-num`(当年大数字,`#1AC9FF` + text-shadow)、`.summary-digits`/`.digit-card`(每位翻牌,`num-img.png` 背景) + +### 3. 改写 `src/views/index.vue` 为极简入口 +```vue + + + +``` +(删除原 `calcXxxHeight`/`$refs`/resize 监听等所有逻辑) + +`trafficFlow/index.vue` 模板骨架: +```vue + +``` + +### 4. 启动验证一轮(`npm run serve`) +确认:Header + "客流量展示" + 三卡片(两折线图初始化、一翻牌)+ 汇总翻牌滚动;控制台无 echarts 报错。 + +### 5. 删除旧组件目录 +- `src/views/accessToLibrary/`、`totalLending/`、`lengingRanking/`、`newBookRecommend/`、`notice/`、`readStar/`、`todayBorrowed/` + +### 6. 可选清理 +- 删 `src/components/echart/`(删旧组件后无引用)、`src/common/scrollMixins.js`(全项目无引用)。 +- 清 `index.scss` 中三栏 `.screen-left/.screen-right/.screen-middle` 及旧组件死 CSS(留着不影响,但建议删三栏那三条以免误导)。 + +## 接口对接预留(mock 阶段不做) +- `src/api/library.js` 的 `FetchInitIntoNum` 已存在,保留不删。后续在 4 个组件的 `fetchData()`(已注释)中对接,字段参考 `historyPeopleNum`(累计)、`dayPeopleNum`/`monthPeopleNum`(当日/当月总数);按小时/按日粒度折线数据需后端补接口。 + +## 验证 +1. `npm run serve` → `http://localhost:8080`: + - Header 显示"黄石图书馆" + 天气 + 每秒跳的时间 + - 顶部"客流量展示"标签 + - 第一行三列:左/中蓝色平滑折线图(带渐变面积)、右"当年进馆"0→1286500 翻牌动画 + - 第二行"累计客流量汇总"8 位翻牌(num-img.png 蓝牌背景)横向滚动 +2. F12 控制台无 echarts init/id 重复/resize 报错 +3. 拖拽窗口:折线图随 resizeMixins 自动 resize,rem 尺寸随 flexible 重算 +4. 改 mock 数字热更新不残留旧 echarts 实例(dispose 生效) +5. `npm run build` 无打包错误 + +## 风险规避 +- echarts 容器高度坍缩:`.traffic-row-3 { min-height:0 }` + `.traffic-card .module-content { flex:1 }` 必须同时有;必要时给 `.chart-box` 兜底 `min-height:2rem`。 +- `seamless` 横向滚动可能因 8 位内容不够触发:调小 `limitMoveNum:1`,或不要滚动直接静态居中("可滚动"非硬需求)。 +- 顺序:先建新→改入口→验证→再删旧,避免白屏。 diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..e763183 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,11 @@ +const plugins = ['@vue/babel-plugin-transform-vue-jsx', '@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-nullish-coalescing-operator'] +// 生产环境移除console +if (process.env.NODE_ENV === 'production') { + plugins.push('transform-remove-console') +} +module.exports = { + plugins: plugins, + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8cfd07c --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "intelligence-screen", + "version": "1.0.0", + "description": "人流数据大屏", + "scripts": { + "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", + "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build", + "lint": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service lint" + }, + "dependencies": { + "@babel/core": "^7.19.0", + "@babel/preset-env": "^7.19.0", + "@jiaminghi/data-view": "^2.7.3", + "@types/echarts": "^4.4.3", + "axios": "^0.27.2", + "core-js": "^3.6.4", + "echarts": "^4.6.0", + "element-ui": "^2.15.10", + "vue": "^2.6.11", + "vue-awesome": "^4.0.2", + "vue-count-to": "^1.0.13", + "vue-router": "^3.1.5", + "vue-seamless-scroll": "^1.1.23", + "vuex": "^3.1.2" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "^4.2.0", + "@vue/cli-plugin-eslint": "^4.2.0", + "@vue/cli-service": "^4.2.0", + "babel-eslint": "^10.0.3", + "babel-plugin-transform-remove-console": "^6.9.4", + "eslint": "^6.7.2", + "eslint-plugin-vue": "^6.1.2", + "sass": "^1.25.0", + "sass-loader": "^8.0.2", + "vue-awesome-swiper": "^3.1.3", + "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" + ] +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..df36fcf 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..34c570f --- /dev/null +++ b/public/index.html @@ -0,0 +1,19 @@ + + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + + + diff --git a/public/static/branches.json b/public/static/branches.json new file mode 100644 index 0000000..8e8d585 --- /dev/null +++ b/public/static/branches.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": "1.0", + "description": "图书馆大屏网点配置:网点编号与网点名称映射", + "branches": [ + { "code": "HWXD", "name": "宏伟星都城市书房" }, + { "code": "JTSF", "name": "中窑江滩城市书房" }, + { "code": "QYG", "name": "群艺馆城市书房" }, + { "code": "QSH", "name": "青山湖城市书房" }, + { "code": "HSGQ", "name": "黄石港区城市书房" }, + { "code": "SDGY", "name": "湿地公园城市书房" }, + { "code": "SNSQ", "name": "神牛社区城市书房" }, + { "code": "TSSF", "name": "铁山城市书房" }, + { "code": "WSW", "name": "未苏湾城市书房" }, + { "code": "SMZJ", "name": "市民之家城市书房" }, + { "code": "RTFLY", "name": "儿童福利院城市书房" }, + { "code": "D3、D4", "name": "保利时光绘城市书房" }, + { "code": "D5", "name": "春湖公园城市书房" } + ] +} diff --git a/public/static/config.js b/public/static/config.js new file mode 100644 index 0000000..ecfc7c0 --- /dev/null +++ b/public/static/config.js @@ -0,0 +1,4 @@ +window.g = { + AXIOS_TIMEOUT: 10000, + ApiUrl: 'http://192.168.99.63:8080' // 配置服务器地址 +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..f26dc75 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/api/library.js b/src/api/library.js new file mode 100644 index 0000000..10f66a8 --- /dev/null +++ b/src/api/library.js @@ -0,0 +1,13 @@ +import request from '@/utils/request' + +export function FetchInitIntoNumHS(params) { + return request({ + url: '/dxhtsg/initIntoNumHS', + method: 'get', + params: params + }) +} + +export default { + FetchInitIntoNumHS +} diff --git a/src/assets/fonts/YouSheBiaoTiHei.ttf b/src/assets/fonts/YouSheBiaoTiHei.ttf new file mode 100644 index 0000000..3729151 Binary files /dev/null and b/src/assets/fonts/YouSheBiaoTiHei.ttf differ diff --git a/src/assets/fonts/ZhenyanGB.ttf b/src/assets/fonts/ZhenyanGB.ttf new file mode 100644 index 0000000..207e103 Binary files /dev/null and b/src/assets/fonts/ZhenyanGB.ttf differ diff --git a/src/assets/fonts/fonts.css b/src/assets/fonts/fonts.css new file mode 100644 index 0000000..1dffefd --- /dev/null +++ b/src/assets/fonts/fonts.css @@ -0,0 +1,13 @@ +@font-face { + font-family: "ZhenyanGB"; + src: url('ZhenyanGB.ttf'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "YouSheBiaoTiHei"; + src: url('YouSheBiaoTiHei.ttf'); + font-weight: normal; + font-style: normal; +} \ No newline at end of file diff --git a/src/assets/iconfont/iconfont.css b/src/assets/iconfont/iconfont.css new file mode 100644 index 0000000..a218315 --- /dev/null +++ b/src/assets/iconfont/iconfont.css @@ -0,0 +1,68 @@ +@font-face { + font-family: "iconfont"; /* Project id 3646564 */ + src: url('iconfont.woff2?t=1663379994606') format('woff2'), + url('iconfont.woff?t=1663379994606') format('woff'), + url('iconfont.ttf?t=1663379994606') format('truetype'), + url('iconfont.svg?t=1663379994606#iconfont') format('svg'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-a-no21:before { + content: "\e636"; +} + +.icon-a-21:before { + content: "\e637"; +} + +.icon-jianpan:before { + content: "\e634"; +} + +.icon-guanbi:before { + content: "\e630"; +} + +.icon-zuo:before { + content: "\e631"; +} + +.icon-benjiatushu:before { + content: "\e632"; +} + +.icon-remen:before { + content: "\e633"; +} + +.icon-you:before { + content: "\e62f"; +} + +.icon-tongzhi:before { + content: "\e628"; +} + +.icon-a-no3:before { + content: "\e629"; +} + +.icon-a-no1:before { + content: "\e62b"; +} + +.icon-a-3:before { + content: "\e62d"; +} + +.icon-a-1:before { + content: "\e62e"; +} + diff --git a/src/assets/iconfont/iconfont.js b/src/assets/iconfont/iconfont.js new file mode 100644 index 0000000..8284bfe --- /dev/null +++ b/src/assets/iconfont/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_3646564='',function(t){var F=(F=document.getElementsByTagName("script"))[F.length-1],a=F.getAttribute("data-injectcss"),F=F.getAttribute("data-disable-injectsvg");if(!F){var l,p,h,i,c,d=function(F,a){a.parentNode.insertBefore(F,a)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(F){console&&console.log(F)}}l=function(){var F,a=document.createElement("div");a.innerHTML=t._iconfont_svg_string_3646564,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(F=document.body).firstChild?d(a,F.firstChild):F.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(p=function(){document.removeEventListener("DOMContentLoaded",p,!1),l()},document.addEventListener("DOMContentLoaded",p,!1)):document.attachEvent&&(h=l,i=t.document,c=!1,M(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,o())})}function o(){c||(c=!0,h())}function M(){try{i.documentElement.doScroll("left")}catch(F){return void setTimeout(M,50)}o()}}(window); \ No newline at end of file diff --git a/src/assets/iconfont/iconfont.json b/src/assets/iconfont/iconfont.json new file mode 100644 index 0000000..00e91de --- /dev/null +++ b/src/assets/iconfont/iconfont.json @@ -0,0 +1,100 @@ +{ + "id": "3646564", + "name": "智慧屏", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "31879702", + "name": "no.2", + "font_class": "a-no21", + "unicode": "e636", + "unicode_decimal": 58934 + }, + { + "icon_id": "31879708", + "name": "2", + "font_class": "a-21", + "unicode": "e637", + "unicode_decimal": 58935 + }, + { + "icon_id": "31879044", + "name": "键盘", + "font_class": "jianpan", + "unicode": "e634", + "unicode_decimal": 58932 + }, + { + "icon_id": "31859389", + "name": "关闭", + "font_class": "guanbi", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "31859390", + "name": "左", + "font_class": "zuo", + "unicode": "e631", + "unicode_decimal": 58929 + }, + { + "icon_id": "31859393", + "name": "本架图书", + "font_class": "benjiatushu", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "31859394", + "name": "热门", + "font_class": "remen", + "unicode": "e633", + "unicode_decimal": 58931 + }, + { + "icon_id": "31859388", + "name": "右", + "font_class": "you", + "unicode": "e62f", + "unicode_decimal": 58927 + }, + { + "icon_id": "31804863", + "name": "通知", + "font_class": "tongzhi", + "unicode": "e628", + "unicode_decimal": 58920 + }, + { + "icon_id": "31804864", + "name": "no.3", + "font_class": "a-no3", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "31804866", + "name": "no.1", + "font_class": "a-no1", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "31804868", + "name": "3", + "font_class": "a-3", + "unicode": "e62d", + "unicode_decimal": 58925 + }, + { + "icon_id": "31804869", + "name": "1", + "font_class": "a-1", + "unicode": "e62e", + "unicode_decimal": 58926 + } + ] +} diff --git a/src/assets/iconfont/iconfont.svg b/src/assets/iconfont/iconfont.svg new file mode 100644 index 0000000..1f96bc6 --- /dev/null +++ b/src/assets/iconfont/iconfont.svg @@ -0,0 +1,45 @@ + + + + Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/iconfont/iconfont.ttf b/src/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000..cd9dd4b Binary files /dev/null and b/src/assets/iconfont/iconfont.ttf differ diff --git a/src/assets/iconfont/iconfont.woff b/src/assets/iconfont/iconfont.woff new file mode 100644 index 0000000..f9c8d7d Binary files /dev/null and b/src/assets/iconfont/iconfont.woff differ diff --git a/src/assets/iconfont/iconfont.woff2 b/src/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000..2552240 Binary files /dev/null and b/src/assets/iconfont/iconfont.woff2 differ diff --git a/src/assets/images/default-img.png b/src/assets/images/default-img.png new file mode 100644 index 0000000..01a60cb Binary files /dev/null and b/src/assets/images/default-img.png differ diff --git a/src/assets/images/header-bg.png b/src/assets/images/header-bg.png new file mode 100644 index 0000000..84d92c1 Binary files /dev/null and b/src/assets/images/header-bg.png differ diff --git a/src/assets/images/header-left.png b/src/assets/images/header-left.png new file mode 100644 index 0000000..c713888 Binary files /dev/null and b/src/assets/images/header-left.png differ diff --git a/src/assets/images/header-right.png b/src/assets/images/header-right.png new file mode 100644 index 0000000..18abf63 Binary files /dev/null and b/src/assets/images/header-right.png differ diff --git a/src/assets/images/notice.gif b/src/assets/images/notice.gif new file mode 100644 index 0000000..17f9f0d Binary files /dev/null and b/src/assets/images/notice.gif differ diff --git a/src/assets/images/num-img.png b/src/assets/images/num-img.png new file mode 100644 index 0000000..01bd009 Binary files /dev/null and b/src/assets/images/num-img.png differ diff --git a/src/assets/images/star-1.png b/src/assets/images/star-1.png new file mode 100644 index 0000000..d2f0f81 Binary files /dev/null and b/src/assets/images/star-1.png differ diff --git a/src/assets/images/star-2.png b/src/assets/images/star-2.png new file mode 100644 index 0000000..c00d713 Binary files /dev/null and b/src/assets/images/star-2.png differ diff --git a/src/assets/images/star-3.png b/src/assets/images/star-3.png new file mode 100644 index 0000000..82407a5 Binary files /dev/null and b/src/assets/images/star-3.png differ diff --git a/src/assets/images/title-big.png b/src/assets/images/title-big.png new file mode 100644 index 0000000..2d5a0b9 Binary files /dev/null and b/src/assets/images/title-big.png differ diff --git a/src/assets/images/title.png b/src/assets/images/title.png new file mode 100644 index 0000000..b5ddaea Binary files /dev/null and b/src/assets/images/title.png differ diff --git a/src/assets/images/top-line.png b/src/assets/images/top-line.png new file mode 100644 index 0000000..33beb85 Binary files /dev/null and b/src/assets/images/top-line.png differ diff --git a/src/assets/images/top.png b/src/assets/images/top.png new file mode 100644 index 0000000..a4e606f Binary files /dev/null and b/src/assets/images/top.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/styles/ball-running-animate.css b/src/assets/styles/ball-running-animate.css new file mode 100644 index 0000000..55fcb8d --- /dev/null +++ b/src/assets/styles/ball-running-animate.css @@ -0,0 +1,116 @@ +.ball-running-dots { + position: absolute; + top: 0.2rem; +} +.left-dots { + left: 136px; +} +.right-dots { + right: 200px; +} +.ball-running-dots > div { + position: relative; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.ball-running-dots { + display: block; + font-size: 0; + color: #316fd9; +} +.ball-running-dots > div { + display: inline-block; + float: none; + background-color: currentColor; + border: 0 solid currentColor; +} +.ball-running-dots { + width: 16px; + height: 16px; +} +.ball-running-dots > div { + position: absolute; + margin-left: 30px; + border-radius: 100%; +} +.left-dots > div { + animation: ball-running-dots-animate 2s linear infinite; +} +.right-dots > div { + animation: ball-running-dots-animate2 2s linear infinite; +} +.ball-running-dots > div:nth-child(1) { + animation-delay: 0s; +} +.ball-running-dots > div:nth-child(2) { + animation-delay: -0.8s; +} +.ball-running-dots > div:nth-child(3) { + animation-delay: -1.2s; +} +.ball-running-dots > div:nth-child(4) { + animation-delay: -1.6s; +} +@keyframes ball-running-dots-animate { + 0%{ + width: 100%; + height: 100%; + transform: translateY(0) translateX(-500%) scale(-.1); + opacity: 0.5; + } + 80% { + transform: translateY(0) translateX(0); + } + 85% { + width: 100%; + height: 100%; + transform: translateY(-125%) translateX(0); + } + 90% { + width: 200%; + height: 75%; + } + 95% { + width: 100%; + height: 100%; + transform: translateY(-100%) translateX(-500%); + } + 100% { + width: 100%; + height: 100%; + transform: translateY(0) translateX(-500%); + opacity: 1; + } +} +@keyframes ball-running-dots-animate2 { + 0%{ + width: 100%; + height: 100%; + transform: translateY(0) translateX(500%) scale(-.1); + opacity: 0.5; + } + 80% { + transform: translateY(0) translateX(0); + } + 85% { + width: 100%; + height: 100%; + transform: translateY(-125%) translateX(0); + } + 90% { + width: 200%; + height: 75%; + } + 95% { + width: 100%; + height: 100%; + transform: translateY(-100%) translateX(500%); + } + 100% { + width: 100%; + height: 100%; + transform: translateY(0) translateX(500%); + opacity: 1; + } +} diff --git a/src/assets/styles/font-some.css b/src/assets/styles/font-some.css new file mode 100644 index 0000000..0460bee --- /dev/null +++ b/src/assets/styles/font-some.css @@ -0,0 +1,46 @@ +.chartNum{ + display: flex; + justify-content: center; + margin-top: 0.25rem; +} +.box-items { + position: relative; + display: flex; + height: 0.6rem; + text-align: center; +} +/*滚动数字设置*/ +.number-item { + display: flex; + justify-content: center; + text-align: center; + width: 0.45rem; + height: 0.6rem; + line-height: 0.6rem; + margin-right: 0.125rem; + border: 1px solid #2C75E0; +} +.number-item span{ + position: relative; + display: inline-block; + width: 100%; + height: 100%; + writing-mode: vertical-rl; + text-orientation: upright; + overflow: hidden; +} +.number-item span i{ + font-style: normal; + position: absolute; + top: 0.1rem; + left: 50%; + font-size: 0.325rem; + font-weight: bold; + color: #1AC9FF; + transform: translate(-50%,0); + transition: transform 1s ease-in-out; + letter-spacing: 10px; +} +.number-item:last-child { + margin-right: 0; +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100644 index 0000000..04518d2 --- /dev/null +++ b/src/assets/styles/index.scss @@ -0,0 +1,710 @@ +#screen-container { + width: 100%; + height: 100vh; + padding: 0 0.175rem; + font-size: 0.25rem; + line-height: 0.35rem; + color: #fff; + background-color: #010326; + overflow: hidden; +} + +// 头部主题标题 +.header-container{ + position: relative; + width: 100%; + height: 1.125rem; + font-size: 0.3rem; + color: #fff; + &::before, + &::after{ + content: ""; + position: absolute; + top: 0; + width: 6.2rem; + height: 1.05rem; + } + &::before{ + left: 0; + background: url('~@/assets/images/header-left.png') no-repeat top center; + } + &::after{ + right: 0; + background: url('~@/assets/images/header-right.png') no-repeat top center; + } + .header-title{ + position: relative; + width: 15.85rem; + height: 1.075rem; + margin: 0 auto; + background: url('~@/assets/images/header-bg.png') no-repeat top center; + background-size: cover; + h2{ + font-size: 0.6rem; + font-family: "ZhenyanGB"; + font-weight: normal; + line-height: 1.125rem; + text-align: center; + letter-spacing: 0.2rem; + background: linear-gradient(180deg, #FFFFFF 0%, #1AC9FF 100%); + background-clip: text; + color: transparent; + } + } + .header-text{ + position: absolute; + top: 0.65rem; + } + .header-weather { + left: 0.6rem; + } + .header-date { + right: 0.375rem; + } +} + +// 头部动态效果 +.line1, +.line2{ + position: absolute; + top: 0; +} + +.line1{ + right: 632px; + transform: rotateY(-180deg); +} + +.line2{ + left: 632px; +} + +.top-line1, +.top-line2{ + display: block; + width: 7.925rem; + height: 1.075rem; + animation: topline 4s linear infinite both; +} + +.top-line1{ + background: url('~@/assets/images/top-line.png') no-repeat; +} + +.top-line2{ + background: url('~@/assets/images/top-line.png') no-repeat; +} + +@keyframes topline { + from { + width: 0; + } + 50%, + to { + width: 643px; + } +} + +// 主题内部部分 +.screen-main{ + display: flex; + justify-content: space-between; + height: calc(100% - 1.38rem); + margin: 0.0625rem 0 0.5rem 0; + padding: 0 0.325rem; + overflow: hidden; + .screen-left, + .screen-right{ + width: 6.375rem; + display: flex; + flex-direction: column; + justify-content: space-between; + } + .screen-middle{ + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + margin: 0 0.5rem; + } + .screen-item{ + margin-top: 0.25rem; + } + .common-title{ + width: 100%; + height: 0.475rem; + line-height: 0.475rem; + padding-left: 0.525rem; + font-size: 0.325rem; + background: url('~@/assets/images/title.png') no-repeat center center; + background-size: cover; + font-family: 'YouSheBiaoTiHei'; + letter-spacing: 0.1rem; + } + .module-content{ + border: 1px solid #2C75E0; + box-shadow: inset 0px 0px 38px 1px rgba(44,117,224,0.5); + } + .small-module{ + height: 3.75rem; + } + .medium-module{ + height: 4.25rem; + } + .big-module{ + // height: 6.8125rem; + } + + //排行榜-推荐 - list + .lending-ranking{ + flex: 1; + .module-content{ + padding: 0 0.25rem; + overflow: hidden; + } + } + .book-list-item{ + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.1rem 0.25rem; + .book-img{ + width: 0.825rem; + height: 1.075rem; + margin-right: 0.35rem; + background: url('~@/assets/images/default-img.png') no-repeat center center; + background-size: contain; + display: flex; + align-items: center; + overflow: hidden; + img{ + display: block; + width: 100%; + // height: 100%; + } + } + .book-info{ + flex: 1; + margin-right: 0.35rem; + h4{ + font-size: 0.25rem; + font-weight: normal; + } + p{ + font-size: 0.2rem; + } + } + .ranking-num{ + display: flex; + justify-content: space-between; + align-items: center; + .icon{ + width: 0.5625rem; + height: 0.5625rem; + margin-right: 0.175rem; + } + } + } + + // 进出馆人数 + .accessToLib-container{ + display: flex; + justify-content: space-between; + text-align: center; + align-items: center; + margin: 0.375rem 0 .25rem 0; + .accessToLib-item{ + width: 2.75rem; + .panel-num{ + display: block; + height: 0.85rem; + margin-top: 0.125rem; + line-height: 0.85rem; + font-size: 0.325rem; + font-weight: bold; + text-align: center; + color: #1AC9FF; + background: url('~@/assets/images/num-img.png') no-repeat center center; + background-size: cover; + } + } + } + + // 视频 + .video-box{ + width: 100%; + height: 5.15rem; + } + + // 阅读之星 + .read-star{ + flex: 1; + .common-title{ + background: url('~@/assets/images/title-big.png') no-repeat center center; + background-size: cover; + } + .module-content{ + padding: .25rem; + overflow: hidden; + .readstart-item{ + display: flex; + justify-content: space-between; + align-items: center; + height: 0.5rem; + padding: 0 0.25rem; + font-size: 0.2rem; + margin-bottom: 0.125rem; + background: linear-gradient(90deg, #0C2157 0%, rgba(44,117,224,0) 51%, #0C2157 100%); + .icon-star-1{ + width: 0.35rem; + height: 0.3rem; + margin-left: -0.05rem; + margin-right: 0.2rem; + } + .icon-star-2{ + width: 0.375rem; + height: 0.3rem; + margin-left: -0.0625rem; + margin-right: 0.2rem; + } + .icon-star-3{ + width: 0.275rem; + height: 0.3rem; + margin-right: 0.25rem; + } + .star-num{ + display: block; + width: 0.25rem; + height: 0.25rem; + margin-right: 0.25rem; + font-size: 0.2rem; + line-height: 0.25rem; + text-align: center; + border-radius: 50%; + color: #1AC9FF; + border: 1px solid #1AC9FF; + } + .star-info{ + flex: 1; + margin-right: 0.25rem; + } + &.star1-bg{ + background: url('~@/assets/images/star-1.png') no-repeat left top; + background-size: contain; + } + &.star2-bg{ + background: url('~@/assets/images/star-2.png') no-repeat left top; + background-size: contain; + } + &.star3-bg{ + background: url('~@/assets/images/star-3.png') no-repeat left top; + background-size: contain; + } + } + + } + } + + // 通知公告 + // .notice{ + // .module-content{ + // display: flex; + // justify-content: space-between; + // width: 100%; + // padding: 0.25rem 0.375rem 0.5rem 0.375rem; + // .notice-icon-gif{ + // display: block; + // width: 2.375rem; + // height: 2.875rem; + // background: url('~@/assets/images/notice.gif') no-repeat left top; + // background-size: contain; + // margin-right: 0.15rem; + // } + // .seamless-warp{ + // width: calc(100vw); + // height: 3.1rem; + // overflow: hidden; + // .notice-txt { + // animation: myMove 20s linear infinite; + // animation-fill-mode: forwards; + // } + // @keyframes myMove { + // 0% { + // transform: translateY(2rem); + // } + // 100% { + // transform: translateY(-6rem); + // } + // } + // } + // } + // } + // 通知公告 + .notice{ + .module-content{ + display: flex; + justify-content: space-between; + width: 100%; + padding: 0.25rem 0.375rem 0.5rem 0.375rem; + .notice-icon-gif{ + display: block; + width: 2.375rem; + height: 2.875rem; + background: url('~@/assets/images/notice.gif') no-repeat left top; + background-size: contain; + margin-right: 0.15rem; + } + .seamless-warp{ + width: calc(100vw); + height: 3.1rem; + overflow: hidden; + } + } + } +} + +// ===== 客流量大屏布局 ===== +// 单栏新布局,叠加在 .screen-main 基础上,不改动原有三栏规则 +.traffic-main{ + flex-direction: column; + justify-content: flex-start; + gap: 0.3rem; + padding: 0.25rem; +} + +// 顶部分类标签 +.traffic-section-title{ + height: 0.55rem; + line-height: 0.55rem; + font-size: 0.375rem; + background: url('~@/assets/images/title-big.png') no-repeat center center; + background-size: cover; +} + +// 行容器 +.traffic-row{ + display: flex; +} +.traffic-row-3{ + flex: 1; + gap: 0.25rem; + min-height: 0; +} + +// 卡片:参考 HTML 的 card 样式,角标 + 顶部渐变线 +.traffic-card{ + position: relative; + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + background: rgba(0, 18, 52, 0.45); + border: 1px solid rgba(26, 201, 255, 0.22); + border-radius: 0.2rem; + box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.06); + overflow: hidden; + &::before{ + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 2px; + background: linear-gradient(90deg, transparent, #1AC9FF, transparent); + opacity: 0.6; + z-index: 2; + } + .card-corner{ + position: absolute; + width: 0.175rem; + height: 0.175rem; + border: 2px solid #1AC9FF; + opacity: 0.8; + z-index: 2; + &.tl{ top: -1px; left: -1px; border-right: none; border-bottom: none; } + &.tr{ top: -1px; right: -1px; border-left: none; border-bottom: none; } + &.bl{ bottom: -1px; left: -1px; border-right: none; border-top: none; } + &.br{ bottom: -1px; right: -1px; border-left: none; border-top: none; } + } + .card-header{ + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.2rem 0.275rem 0; + flex-shrink: 0; + } + .card-label{ + display: flex; + align-items: center; + gap: 0.15rem; + font-size: 0.3rem; + font-weight: 600; + color: #f1f5f9; + &::before{ + content: ""; + width: 0.075rem; + height: 0.3rem; + background: #1AC9FF; + border-radius: 3px; + box-shadow: 0 0 0.1rem #1AC9FF; + } + } + .card-date{ + font-size: 0.225rem; + color: #64748b; + font-variant-numeric: tabular-nums; + } + .module-content{ + flex: 1; + overflow: hidden; + padding: 0.15rem 0.275rem 0.275rem; + border: none; + box-shadow: none; + } + .year-content{ + display: flex; + align-items: center; + justify-content: center; + } +} + +// 折线图容器 +.chart-box{ + width: 100%; + height: 100%; + min-height: 2rem; +} + +// 当日进馆 hero 卡片:特殊背景 + 发光边框 + 数字脉冲(仿参考页) +.card-hero{ + background: + radial-gradient(130% 110% at 50% 0%, rgba(26, 201, 255, 0.22) 0%, rgba(26, 201, 255, 0.05) 42%, transparent 72%), + linear-gradient(160deg, #042a52 0%, #021a36 58%, #020f24 100%); + border: 1px solid rgba(26, 201, 255, 0.55); + box-shadow: + 0 0 0.3rem rgba(26, 201, 255, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.08); + &::before{ + height: 3px; + opacity: 1; + background: linear-gradient(90deg, transparent, #1AC9FF, #7dd3fc, #1AC9FF, transparent); + } + .card-corner{ + border-color: #1AC9FF; + opacity: 1; + width: 0.2rem; + height: 0.2rem; + } + .card-label::before{ + box-shadow: 0 0 0.14rem #1AC9FF; + } + .stat-number{ + text-shadow: 0 0 0.55rem rgba(26, 201, 255, 0.65); + animation: heroPulse 2.6s ease-in-out infinite; + } +} +@keyframes heroPulse{ + 0%, 100% { text-shadow: 0 0 0.42rem rgba(26, 201, 255, 0.42); } + 50% { text-shadow: 0 0 0.62rem rgba(26, 201, 255, 0.82); } +} + +// 当日进馆 - 数字卡片 +.stat-card{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + text-align: center; + .live-row{ + margin-bottom: 0.225rem; + display: flex; + align-items: center; + .live-text{ font-size: 0.275rem; color: #94a3b8; } + } + .stat-number{ + font-size: 1.5rem; + font-weight: 600; + line-height: 1; + font-variant-numeric: tabular-nums; + background: linear-gradient(180deg, #fff 0%, #7dd3fc 60%, #1AC9FF 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 0.5rem rgba(26, 201, 255, 0.25); + margin-bottom: 0.15rem; + } + .stat-unit{ + font-size: 0.3rem; + color: #94a3b8; + font-weight: 500; + } +} + +.live-dot{ + display: inline-block; + width: 0.15rem; + height: 0.15rem; + background: #34d399; + border-radius: 50%; + box-shadow: 0 0 0.125rem #34d399; + margin-right: 0.1rem; + animation: livePulse 1.4s infinite; +} +@keyframes livePulse{ + 0%, 100%{ opacity: 1; transform: scale(1); } + 50%{ opacity: 0.6; transform: scale(1.3); } +} + +// 汇总行 +.traffic-summary{ + flex: 1.1; + min-height: 0; + .traffic-summary-inner{ + position: relative; + flex: 1; + display: flex; + flex-direction: column; + width: 100%; + background: rgba(0, 18, 52, 0.45); + border: 1px solid rgba(26, 201, 255, 0.22); + border-radius: 0.2rem; + box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.06); + overflow: hidden; + &::before{ + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 2px; + background: linear-gradient(90deg, transparent, #1AC9FF, transparent); + opacity: 0.6; + z-index: 2; + } + .card-corner{ + position: absolute; + width: 0.175rem; + height: 0.175rem; + border: 2px solid #1AC9FF; + opacity: 0.8; + z-index: 2; + &.tl{ top: -1px; left: -1px; border-right: none; border-bottom: none; } + &.tr{ top: -1px; right: -1px; border-left: none; border-bottom: none; } + &.bl{ bottom: -1px; left: -1px; border-right: none; border-top: none; } + &.br{ bottom: -1px; right: -1px; border-left: none; border-top: none; } + } + } + .module-content{ + flex: 1; + display: flex; + align-items: center; + overflow: hidden; + border: none; + box-shadow: none; + padding: 0; + } +} + +// 累计汇总 - 三栏 +.cumulative-card{ + display: grid; + grid-template-columns: 4.25rem 1fr 3.25rem; + width: 100%; + height: 100%; + padding: 0.35rem 0.45rem; + gap: 0.4rem; + align-items: center; +} +.cumulative-left{ + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.2rem; + // padding-right: 0.4rem; + border-right: 1px solid rgba(26, 201, 255, 0.22); + .cumulative-title{ font-size: 0.5rem; font-weight: 700; color: #f1f5f9; } + .cumulative-subtitle{ font-size: 0.24rem; color: #94a3b8; line-height: 1.5; } +} +.mini-bars{ + display: flex; + align-items: flex-end; + gap: 0.2rem; + margin-top: 0.125rem; + .mini-item{ display: flex; flex-direction: column; align-items: center; gap: 0.1rem; } + .mini-col{ height: 1.3rem; display: flex; align-items: flex-end; } + .mini-bar{ + width: 0.375rem; + border-radius: 4px 4px 0 0; + background: linear-gradient(180deg, #1AC9FF 0%, rgba(26, 201, 255, 0.15) 100%); + box-shadow: 0 0 0.125rem rgba(26, 201, 255, 0.25); + } + .mini-label{ font-size: 0.15rem; color: #64748b; } +} +.cumulative-right{ + display: flex; + align-items: center; + justify-content: center; + gap: 0.2rem; + position: relative; + &::before{ + content: ""; + position: absolute; + width: 6.5rem; + height: 3.25rem; + background: radial-gradient(ellipse at center, rgba(26, 201, 255, 0.12) 0%, transparent 70%); + pointer-events: none; + } + .digit-box{ + width: 1.5rem; + height: 2.05rem; + background: linear-gradient(180deg, rgba(26, 201, 255, 0.22) 0%, rgba(26, 201, 255, 0.06) 100%); + border: 1px solid rgba(26, 201, 255, 0.22); + border-radius: 0.125rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.35rem; + font-weight: 800; + font-variant-numeric: tabular-nums; + color: #f1f5f9; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 4px 0.25rem rgba(0, 0, 0, 0.25); + text-shadow: 0 0 0.25rem rgba(26, 201, 255, 0.35); + position: relative; + overflow: hidden; + &::after{ + content: ""; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: rgba(0, 0, 0, 0.25); + z-index: 2; + } + // 翻牌滚动列表 + .digit-roller{ + display: flex; + flex-direction: column; + transition: transform 1.4s cubic-bezier(0.22, 1, 0.36, 1); + .digit-cell{ + display: flex; + align-items: center; + justify-content: center; + height: 2.05rem; + flex-shrink: 0; + } + } + } +} +.cumulative-illustration{ + display: flex; + align-items: center; + justify-content: center; + padding-left: 0.25rem; + svg{ + width: 3rem; + height: 3rem; + filter: drop-shadow(0 0 0.3rem rgba(26, 201, 255, 0.3)); + } +} \ No newline at end of file diff --git a/src/assets/styles/style.scss b/src/assets/styles/style.scss new file mode 100644 index 0000000..66f20e8 --- /dev/null +++ b/src/assets/styles/style.scss @@ -0,0 +1,93 @@ +@import '~@/assets/iconfont/iconfont.css'; + +// 全局样式 +* { + margin: 0; + padding: 0; + list-style-type: none; + box-sizing: border-box; + outline: none; +} + +html { + margin: 0; + padding: 0; +} + +body { + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; + background-color: #f1f1f1; + margin: 0; + padding: 0; +} + +a { + color: #343440; + text-decoration: none; +} + +ul{ + margin: 0; + padding: 0; + li{ + list-style: none; + } +} + +img[src=""],img:not([src]){ + opacity: 0; + border:none; + visibility: hidden; + max-width: none; +} + +.clearfix { + &::after { + content: ""; + display: table; + height: 0; + line-height: 0; + visibility: hidden; + clear: both; + } +} + +pre{ + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; + word-wrap: break-word; + white-space: pre-wrap; +} + +/**滚动条的宽度*/ +::-webkit-scrollbar { + width: 4px; + height: 4px; +} + +//滚动条的滑块 +::-webkit-scrollbar-thumb { + background-color: #13439E; + border-radius: 3px; +} + +.icon { + width: 1em; + height: 1em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +//浮动 +.float-r { + float: right; +} + +//文章一行显示,多余省略号显示 +.title-item { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/src/common/flexible.js b/src/common/flexible.js new file mode 100644 index 0000000..fdb9fa5 --- /dev/null +++ b/src/common/flexible.js @@ -0,0 +1,147 @@ +(function(win, lib) { + var doc = win.document + var docEl = doc.documentElement + var metaEl = doc.querySelector('meta[name="viewport"]') + var flexibleEl = doc.querySelector('meta[name="flexible"]') + var dpr = 0 + var scale = 0 + var tid + var flexible = lib.flexible || (lib.flexible = {}) + + if (metaEl) { + console.warn('将根据已有的meta标签来设置缩放比例') + var match = metaEl + .getAttribute('content') + // eslint-disable-next-line no-useless-escape + .match(/initial\-scale=([\d\.]+)/) + if (match) { + scale = parseFloat(match[1]) + dpr = parseInt(1 / scale) + } + } else if (flexibleEl) { + var content = flexibleEl.getAttribute('content') + if (content) { + // eslint-disable-next-line no-useless-escape + var initialDpr = content.match(/initial\-dpr=([\d\.]+)/) + // eslint-disable-next-line no-useless-escape + var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/) + if (initialDpr) { + dpr = parseFloat(initialDpr[1]) + scale = parseFloat((1 / dpr).toFixed(2)) + } + if (maximumDpr) { + dpr = parseFloat(maximumDpr[1]) + scale = parseFloat((1 / dpr).toFixed(2)) + } + } + } + + if (!dpr && !scale) { + // eslint-disable-next-line no-unused-vars + var isAndroid = win.navigator.appVersion.match(/android/gi) + var isIPhone = win.navigator.appVersion.match(/iphone/gi) + var devicePixelRatio = win.devicePixelRatio + if (isIPhone) { + // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 + if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { + dpr = 3 + } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) { + dpr = 2 + } else { + dpr = 1 + } + } else { + // 其他设备下,仍旧使用1倍的方案 + dpr = 1 + } + scale = 1 / dpr + } + + docEl.setAttribute('data-dpr', dpr) + if (!metaEl) { + metaEl = doc.createElement('meta') + metaEl.setAttribute('name', 'viewport') + metaEl.setAttribute( + 'content', + 'initial-scale=' + + scale + + ', maximum-scale=' + + scale + + ', minimum-scale=' + + scale + + ', user-scalable=no' + ) + if (docEl.firstElementChild) { + docEl.firstElementChild.appendChild(metaEl) + } else { + var wrap = doc.createElement('div') + wrap.appendChild(metaEl) + doc.write(wrap.innerHTML) + } + } + + function refreshRem() { + var width = docEl.getBoundingClientRect().width + // 最小1366px,最大适配2560px + if (width / dpr < 1366) { + width = 1366 * dpr + } else if (width / dpr > 2560) { + width = 2560 * dpr + } + // 设置成24等份,设计稿时1920px的,这样1rem就是80px + var rem = width / 24 + docEl.style.fontSize = rem + 'px' + flexible.rem = win.rem = rem + } + + win.addEventListener( + 'resize', + function() { + clearTimeout(tid) + tid = setTimeout(refreshRem, 300) + }, + false + ) + win.addEventListener( + 'pageshow', + function(e) { + if (e.persisted) { + clearTimeout(tid) + tid = setTimeout(refreshRem, 300) + } + }, + false + ) + + if (doc.readyState === 'complete') { + doc.body.style.fontSize = 12 * dpr + 'px' + } else { + doc.addEventListener( + 'DOMContentLoaded', + // eslint-disable-next-line no-unused-vars + function(e) { + doc.body.style.fontSize = 12 * dpr + 'px' + }, + false + ) + } + + refreshRem() + + flexible.dpr = win.dpr = dpr + flexible.refreshRem = refreshRem + flexible.rem2px = function(d) { + var val = parseFloat(d) * this.rem + if (typeof d === 'string' && d.match(/rem$/)) { + val += 'px' + } + return val + } + flexible.px2rem = function(d) { + var val = parseFloat(d) / this.rem + if (typeof d === 'string' && d.match(/px$/)) { + val += 'rem' + } + return val + } +})(window, window['lib'] || (window['lib'] = {})) diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..84a19b6 --- /dev/null +++ b/src/main.js @@ -0,0 +1,49 @@ +import Vue from 'vue' +import App from './App.vue' +import router from './router' +import store from './store' + +import ElementUI from 'element-ui' +import 'element-ui/lib/theme-chalk/index.css' +Vue.use(ElementUI) + +// 适配flex +import '@/common/flexible.js' + +// 引入全局css +import './assets/styles/style.scss' +import './assets/iconfont/iconfont.js' +import './assets/fonts/fonts.css' + +// 按需引入然后注册在vue原型上 +import { Message } from 'element-ui' +Vue.prototype.$message = Message + +// 全局注册过滤 - 时间 +import { parseTime, getFormattedDate } from '@/utils/index.js' +Vue.filter('parseTime', function(time, cFormat) { + return parseTime(time, cFormat) +}) +Vue.prototype.getFormattedDate = getFormattedDate + +// 馆代码 1201为东西湖馆代码 / 本地测试用FTZN +// Vue.prototype.libcode = 'FTZN' +Vue.prototype.libcode = '1201' + +import axios from 'axios' +Vue.prototype.$axios = axios + +// 引入echart +import echarts from 'echarts' +Vue.prototype.$echarts = echarts + +import scroll from 'vue-seamless-scroll' +Vue.use(scroll) + +Vue.config.productionTip = false +Vue.component(Message.name, Message) +new Vue({ + router, + store, + render: (h) => h(App) +}).$mount('#app') diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..2611efe --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +Vue.use(VueRouter) + +const routes = [ + { + path: '/', + name: 'index', + component: () => import('../views/index.vue'), + meta: { + title: '人流数据大屏' + } + } +] + +const router = new VueRouter({ + mode: 'hash', + routes +}) + +export default router diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..b2f39d8 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.use(Vuex) + +export default new Vuex.Store({ + state: { + }, + mutations: { + }, + actions: { + }, + modules: { + } +}) diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..6f2d4e5 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,100 @@ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * Parse the time to string + * @param {(Object|string|number)} time + * @param {string} cFormat + * @returns {string} + */ +export function parseTime(time, cFormat) { + if (arguments.length === 0) { + return null + } + const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'undefined' || time === null || time === 'null') { + return '' + } else if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } + 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(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 获取当前日期时间 +export function getCurrentTime() { + const yy = new Date().getFullYear() + const mm = new Date().getMonth() + 1 + const dd = new Date().getDate() + const hh = new Date().getHours() + const mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date().getMinutes() + const ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date().getSeconds() + const time = yy + '年' + mm + '月' + dd + '日 ' + hh + ':' + mf + ':' + ss + return time +} + +export function getFormattedDate(date, yearOffset = 0) { + date.setFullYear(date.getFullYear() + yearOffset) + const year = date.getFullYear() + const month = (date.getMonth() + 1).toString().padStart(2, '0') + const day = date.getDate().toString().padStart(2, '0') + return `${year}-${month}-${day}` +} diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..5571de8 --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,39 @@ +import axios from 'axios' +// import { Message } from 'element-ui' + +// 创建axios实例 +// 生产环境 baseURL 走 public/static/config.js 的 window.g.ApiUrl,部署后改 config 即可切换后端,无需重新打包 +const service = axios.create({ + baseURL: process.env.NODE_ENV === 'production' ? ((window.g && window.g.ApiUrl) || '') : '/', + timeout: (window.g && window.g.AXIOS_TIMEOUT) || 1000 * 30, // 请求超时时间 + headers: { + 'Content-Type': 'application/json' + } +}) + +// request拦截器 +service.interceptors.request.use( + config => { + // if (getToken()) { + // config.headers['Authorization'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + // } + const token = '' // 登录后生成用于识别用户身份,项目不需要直接去掉 + config.headers['Authorization'] = token || '' + return config + }, + error => { + console.error('error: ', error) + Promise.reject(error) + } +) + +// response 拦截器 +service.interceptors.response.use( + response => { + return response.data + }, + error => { + return Promise.reject(error) + } +) +export default service diff --git a/src/utils/resizeMixins.js b/src/utils/resizeMixins.js new file mode 100644 index 0000000..dbdffd8 --- /dev/null +++ b/src/utils/resizeMixins.js @@ -0,0 +1,26 @@ +// 混入代码 resize-mixins.js +import { debounce } from '@/utils/index' +const resizeChartMethod = '$__resizeChartMethod' + +export default { + data() { + // 在组件内部将图表init的引用映射到chart属性上 + return { + chart: null + } + }, + created() { + window.addEventListener('resize', this[resizeChartMethod], false) + }, + beforeDestroy() { + window.removeEventListener('reisze', this[resizeChartMethod]) + }, + methods: { + // 通过lodash的防抖函数来控制resize的频率 + [resizeChartMethod]: debounce(function() { + if (this.chart) { + this.chart.resize() + } + }, 100) + } +} diff --git a/src/views/header/index.vue b/src/views/header/index.vue new file mode 100644 index 0000000..d69b897 --- /dev/null +++ b/src/views/header/index.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/views/index.vue b/src/views/index.vue new file mode 100644 index 0000000..a87a8f3 --- /dev/null +++ b/src/views/index.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/trafficFlow/components/DayInFlowCard.vue b/src/views/trafficFlow/components/DayInFlowCard.vue new file mode 100644 index 0000000..b3514b3 --- /dev/null +++ b/src/views/trafficFlow/components/DayInFlowCard.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/views/trafficFlow/components/MonthInFlowChart.vue b/src/views/trafficFlow/components/MonthInFlowChart.vue new file mode 100644 index 0000000..76f6c58 --- /dev/null +++ b/src/views/trafficFlow/components/MonthInFlowChart.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/views/trafficFlow/components/TotalFlowSummary.vue b/src/views/trafficFlow/components/TotalFlowSummary.vue new file mode 100644 index 0000000..c588a8c --- /dev/null +++ b/src/views/trafficFlow/components/TotalFlowSummary.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/views/trafficFlow/components/YearInFlowChart.vue b/src/views/trafficFlow/components/YearInFlowChart.vue new file mode 100644 index 0000000..51fb5bc --- /dev/null +++ b/src/views/trafficFlow/components/YearInFlowChart.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/views/trafficFlow/index.vue b/src/views/trafficFlow/index.vue new file mode 100644 index 0000000..98426d3 --- /dev/null +++ b/src/views/trafficFlow/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..239c12b --- /dev/null +++ b/vue.config.js @@ -0,0 +1,68 @@ +const path = require('path') +const resolve = dir => { + return path.join(__dirname, dir) +} +const name = '人流数据大屏' +module.exports = { + publicPath: process.env.NODE_ENV === 'development' ? '/' : './', + outputDir: 'dist', + assetsDir: 'static', + lintOnSave: process.env.NODE_ENV === 'development', + productionSourceMap: false, + devServer: { + port: 8080, + open: true, + overlay: { + warnings: false, + errors: true + }, + proxy: { + '/dxhtsg/': { + target: process.env.VUE_APP_BASE_API, + changeOrigin: true, + pathRewrite: { + '^/dxhtsg': 'dxhtsg' + } + }, + '/qyzt/': { + target: process.env.VUE_APP_BASE_API, + changeOrigin: true, + pathRewrite: { + '^/qyzt': 'qyzt' + } + }, + '/auth/': { + target: process.env.VUE_APP_BASE_API, + changeOrigin: true, + pathRewrite: { + '^/auth': 'auth' + } + } + } + }, + configureWebpack: { + name: name, + resolve: { + alias: { + '@': resolve('src') + } + }, + performance: { + hints: 'warning', + // 入口起点的最大体积 + maxEntrypointSize: 50000000, + // 生成文件的最大体积 + maxAssetSize: 30000000 + } + }, + chainWebpack: config => { + config.resolve + .alias.set('_c', resolve('src/components')) + + config.plugin('html') + .tap(args => { + args[0].title = '人流数据大屏' + return args + }) + } +}