|
@ -0,0 +1,29 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
components.d.ts
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 daidai
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
322
README.md
|
@ -0,0 +1,322 @@
|
|||
|
||||
## 项目描述
|
||||
|
||||
[IofTV-Screen](https://gitee.com/daidaibg/IofTV-Screen/tree/main) 的 Vue3+vite版本,
|
||||
|
||||
### 与vue2版本对比
|
||||
|
||||
#### 功能
|
||||
|
||||
功能采用与vue2版本相同功能
|
||||
|
||||
因为要与vue2版本相同功能,有些组件不兼容vue3版本,例如:胶囊柱图,数字滚动皆重新封装为组件,整体来说,功能属实相同。根据自己需求选择[vue2](#vue2版本地址)版本与[vue3](#本项目地址 vue3+vite)版本
|
||||
|
||||
#### 样式
|
||||
|
||||
进行微调,整体看着更加美观
|
||||
|
||||
|
||||
|
||||
- 项目需要全屏展示(按 F11)。
|
||||
|
||||
- 项目部分区域使用了全局注册方式,增加了打包体积,在实际运用中请使用 **按需引入**。
|
||||
|
||||
- 项目环境:Vite、Echarts、Npm、Node,axios,mock,vue3。
|
||||
|
||||
- 请拉取 master 分支的代码,其余分支是开发分支。
|
||||
|
||||
- 在项目public目录下存放地图数据合集,根据地市编存放。
|
||||
|
||||
|
||||
友情链接:
|
||||
|
||||
1. [Vue 官方文档](https://cn.vuejs.org/)
|
||||
3. [echarts 实例](https://gitee.com/link?target=https%3A%2F%2Fecharts.apache.org%2Fexamples%2Fzh%2Findex.html),[echarts API 文档](https://gitee.com/link?target=https%3A%2F%2Fecharts.apache.org%2Fzh%2Fapi.html%23echarts)
|
||||
4. [mock.js官网](http://mockjs.com/examples.html)
|
||||
5. [axios官网](https://axios-http.com/)
|
||||
|
||||
**项目展示**
|
||||
|
||||

|
||||
|
||||
### 项目预览地址
|
||||
|
||||
[https://www.daidaibg.com/bigscreen-vue3](https://www.daidaibg.com/bigscreen-vue3)
|
||||
|
||||
### 项目仓库地址
|
||||
|
||||
#### 本项目地址 vue3+vite
|
||||
|
||||
**github地址**
|
||||
|
||||
[https://github.com/daidaibg/IofTV-Screen-Vue3](https://github.com/daidaibg/IofTV-Screen-Vue3)
|
||||
|
||||
**Gitee地址**
|
||||
|
||||
[https://gitee.com/daidaibg/IofTV-Screen-Vue3](https://gitee.com/daidaibg/IofTV-Screen-Vue3)
|
||||
|
||||
#### vue2版本地址
|
||||
|
||||
**github地址**
|
||||
|
||||
[https://github.com/daidaibg/IofTV-Screen](https://github.com/daidaibg/IofTV-Screen)
|
||||
|
||||
**Gitee地址**
|
||||
|
||||
[https://gitee.com/daidaibg/IofTV-Screen](https://gitee.com/daidaibg/IofTV-Screen)
|
||||
|
||||
|
||||
|
||||
### 采用自适应组件方式,
|
||||
|
||||
### 滚动设置,自适应设置
|
||||
|
||||
项目中可以进行滚动配置,内容是否滚动
|
||||
|
||||
点击右上角设置按钮
|
||||

|
||||
|
||||
|
||||
|
||||
可以进行以下配置,可以自行代码中进行修改或增加配置
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 2、主要文件介绍
|
||||
|
||||
| 文件 | 作用/功能 |
|
||||
| ----------------- | ------------------------------------------------------------ |
|
||||
| main.js | 主目录文件,引入 Echart/DataV 等文件 |
|
||||
| utils | 工具函数与 mixins 函数等 |
|
||||
| views/ home.vue | 项目主结构 |
|
||||
| views/其余文件 | 界面各个区域组件(按照位置来命名) |
|
||||
| assets | 静态资源目录,放置 logo 与背景图片 |
|
||||
| assets / css/ | 通用 CSS 文件,全局项目快捷样式调节 |
|
||||
| components/echart | 所有 echart 图表(按照位置来命名) |
|
||||
| common/... | 全局封装的 ECharts 和 flexible 插件代码(适配屏幕尺寸,可定制化修改) |
|
||||
| api/api.js | 接口封装文件 |
|
||||
| mock | 模拟数据接口地址 |
|
||||
|
||||
###
|
||||
|
||||
## 使用介绍
|
||||
|
||||
### 安装
|
||||
|
||||
```npm
|
||||
npm install
|
||||
```
|
||||
### 启动
|
||||
|
||||
```npm
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 取消mock模拟数据
|
||||
|
||||
```javascript
|
||||
// src\main.ts文件
|
||||
把下面两行代码注释掉就可以了。
|
||||
import { mockXHR } from "@/mock/index";
|
||||
mockXHR()
|
||||
```
|
||||
|
||||
##
|
||||
|
||||
## 公用组件
|
||||
|
||||
封装了除面条外个别用到的组件
|
||||
|
||||
### 自适应缩放组件
|
||||
|
||||
#### 注意
|
||||
|
||||
采用Scale方式,会自动给组件父元素添加overflow:hidden
|
||||
|
||||
#### 使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<scale-screen width="1920" height="1080">
|
||||
<div>
|
||||
content
|
||||
</div>
|
||||
</scale-screen>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScaleScreen from 'scale-screen'
|
||||
|
||||
export default {
|
||||
name:'Demo',
|
||||
components:{
|
||||
VScaleScreen
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### API
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 |
|
||||
| ------------ | ------------------------------------------------------------ | -------------------------------- | ------ |
|
||||
| selfAdaption | 是否进行自适应 | Boolean | true |
|
||||
| width | 大屏宽度 | `Number` or `String` | 1920 |
|
||||
| height | 大屏高度 | `Number` or `String` | 1080 |
|
||||
| autoScale | 自适应配置,配置为boolean类型时,为启动或者关闭自适应,配置为对象时,若x为true,x轴产生边距,y为true时,y轴产生边距,启用fullScreen时此配置失效 | Boolean or {x:boolean,y:boolean} | true |
|
||||
| delay | 窗口变化防抖延迟时间 | Number | 500 |
|
||||
| fullScreen | 全屏自适应,启用此配置项时会存在拉伸效果,同时autoScale失效,非必要情况下不建议开启 | Boolean | false |
|
||||
| boxStyle | 修改容器样式,如居中展示时侧边背景色,符合Vue双向绑定style标准格式 | Object | null |
|
||||
| wrapperStyle | 修改自适应区域样式,符合Vue双向绑定style标准格式 | Object | null |
|
||||
|
||||
|
||||
### 外边框
|
||||
|
||||
因为我的项目外边框几乎一样,还有title,所以封装了此组件。
|
||||
|
||||
根据自己需求更改,更换外边框(src\components\item-wrap\item-wrap.vue)下更换。
|
||||
|
||||
```vue
|
||||
<ItemWrap
|
||||
title="我是title"
|
||||
>
|
||||
<div>我是谁?</div>
|
||||
</ItemWrap>
|
||||
```
|
||||
|
||||
| 参数 | 描述 | 默认值 | 类型 | 可选值 |
|
||||
| :---: | :--: | :----: | :----: | :----: |
|
||||
| title | 标头 | - | string | - |
|
||||
|
||||
### CountUp 数字滚动
|
||||
|
||||
以下属性同 coutup.js 配置项(same as countup.js properties)
|
||||
|
||||
#### Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | ---------------- | ------- | ------------------------------------------------------------ |
|
||||
| endVal | Number \| String | - | 结束值 |
|
||||
| startVal | Number \| String | 0 | 起始值 |
|
||||
| duration | Number | 2.5 | 动画时长,单位:秒 |
|
||||
| options | Object | - | [countUp.js](https://github.com/inorganik/countUp.js) options 配置项 |
|
||||
|
||||
以下为组件特有属性(extension properties)
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | ----------------- | ------- | ----------------------------- |
|
||||
| autoplay | Boolean | true | 是否自动计数 |
|
||||
| loop | Boolean \| Number | false | 循环次数,有限次数 / 无限循环 |
|
||||
| delay | Number | 0 | loop 循环的间隔时间,单位:秒 |
|
||||
|
||||
#### 插槽(slots)
|
||||
|
||||
| Name | Description |
|
||||
| ------ | ----------- |
|
||||
| prefix | 前缀 |
|
||||
| suffix | 后缀 |
|
||||
|
||||
#### 事件(Events)
|
||||
|
||||
| Name | Description | return |
|
||||
| --------- | -------------------------- | ------------ |
|
||||
| @init | CountUp 实例初始化完成触发 | CountUp 实例 |
|
||||
| @finished | 计数结束时触发 | - |
|
||||
|
||||
#### countup.js 配置项说明
|
||||
```ts
|
||||
interface CountUpOptions {
|
||||
startVal?: number // number to start at (0) 开始数值,默认 0
|
||||
decimalPlaces?: number // number of decimal places (0) 小数点 位数
|
||||
duration?: number // animation duration in seconds (2) 动画时长
|
||||
useGrouping?: boolean // example: 1,000 vs 1000 (true) 是否使用千分位
|
||||
useEasing?: boolean // ease animation (true) 是否开启动画过渡,默认动画函数为easeOutExpo
|
||||
smartEasingThreshold?: number // smooth easing for large numbers above this if useEasing (999)
|
||||
smartEasingAmount?: number // amount to be eased for numbers above threshold (333)
|
||||
separator?: string // grouping separator (',') 千分位分隔符
|
||||
decimal?: string // decimal ('.') 小数点分隔符
|
||||
// easingFn: easing function for animation (easeOutExpo) 动画函数
|
||||
easingFn?: (t: number, b: number, c: number, d: number) => number
|
||||
formattingFn?: (n: number) => string // this function formats result 格式化结果
|
||||
prefix?: string // text prepended to result 数值前缀
|
||||
suffix?: string // text appended to result 数值后缀
|
||||
numerals?: string[] // numeral glyph substitution 数字符号替换 0 - 9,例如替换为 [a,b,c,d,e,f,g,h,i,j]
|
||||
enableScrollSpy?: boolean // start animation when target is in view 在可视范围内才开始动画
|
||||
scrollSpyDelay?: number // delay (ms) after target comes into view 目标进入可视范围内后的延迟时间(毫秒)
|
||||
}
|
||||
```
|
||||
|
||||
### 胶囊柱图
|
||||
|
||||
#### Props
|
||||
|
||||
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
| :----: | :------: | :-------------: | :-----------------------: | :-----: |
|
||||
| data | 柱数据 | `Array<Object>` | [data属性](#data属性) | `[]` |
|
||||
| config | 基础配置 | Object | [config属性](#config属性) | `false` |
|
||||
|
||||
#### config属性
|
||||
|
||||
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
| :-------: | :------: | :-------------: | :----: | :-----: |
|
||||
| unit | 单位 | `String` | --- | `''` |
|
||||
| colors | 环颜色 | `Array<String>` | [1] | [2] |
|
||||
| showValue | 显示数值 | `Boolean` | --- | `false` |
|
||||
|
||||
#### 注释config注释
|
||||
|
||||
[1] 颜色支持`hex|rgb|rgba|颜色关键字`等四种类型。
|
||||
|
||||
[2] 默认配色为`['#37a2da', '#32c5e9', '#67e0e3', '#9fe6b8', '#ffdb5c', '#ff9f7f', '#fb7293']`。
|
||||
|
||||
#### data属性
|
||||
|
||||
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
| :---: | :------: | :------: | :----: | :----: |
|
||||
| name | 柱名称 | `String` | --- | --- |
|
||||
| value | 柱对应值 | `Number` | --- | --- |
|
||||
|
||||
### 无缝轮播组件
|
||||
|
||||
看此文档 优化次源码
|
||||
|
||||
[https://doc.wssio.com/opensource/vue3-seamless-scroll/](https://doc.wssio.com/opensource/vue3-seamless-scroll/)
|
||||
|
||||
## 中间地图
|
||||
|
||||
### 南海显隐控制
|
||||
|
||||
根据需求来,**修改此值请刷新页面**
|
||||
|
||||
```indexs/center-map.vue``` 文件中```isSouthChinaSea```变量 默认不显示南海(false),为```true```的时候显示南海
|
||||
|
||||
```
|
||||
isSouthChinaSea:false,//默认不显示南海,改为true可显示南海
|
||||
```
|
||||
|
||||
## 全局参数
|
||||
|
||||
### filter
|
||||
|
||||
监测数据项统一过滤,保留两位小数。
|
||||
|
||||
```vue
|
||||
{{10.23123|montionFilter }}
|
||||
```
|
||||
|
||||
## 大屏交流反馈(面条的群)
|
||||
|
||||
### 大屏QQ群
|
||||
|
||||
QQ群号:
|
||||
|
||||
一群:713105837 (已满)
|
||||
|
||||
二群:495755841
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>IofTV-Screen-Vue3</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "test",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build:old": "run-p type-check build-only",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 4173",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"countup.js": "^2.8.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "^2.6.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"mockjs": "^1.1.0",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-echarts": "^6.6.9",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/mockjs": "^1.0.10",
|
||||
"@types/node": "^20.11.30",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.38",
|
||||
"sass": "^1.72.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "~5.4.3",
|
||||
"unplugin-auto-import": "^0.17.5",
|
||||
"unplugin-element-plus": "^0.8.0",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.2.6",
|
||||
"vue-tsc": "^2.0.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
After Width: | Height: | Size: 162 KiB |
|
@ -0,0 +1 @@
|
|||
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":710000,"name":"台湾省","center":[121.509062,25.044332],"centroid":[120.971485,23.749452],"childrenNum":0,"level":"province","acroutes":[100000],"parent":{"adcode":100000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[120.443558,22.441245],[120.517584,22.408536],[120.569903,22.361728],[120.640505,22.241347],[120.659209,22.15432],[120.662001,22.066983],[120.651464,22.033165],[120.667691,21.983168],[120.70157,21.927065],[120.743246,21.915569],[120.78155,21.923957],[120.85468,21.883333],[120.87291,21.897387],[120.866482,21.98436],[120.907315,22.033208],[120.904154,22.119757],[120.914955,22.302718],[120.981658,22.528305],[121.015009,22.584168],[121.033292,22.650725],[121.078498,22.669656],[121.170544,22.723133],[121.210481,22.770665],[121.237931,22.836327],[121.324708,22.945666],[121.354687,23.01006],[121.370388,23.084347],[121.409535,23.102669],[121.430294,23.137196],[121.415015,23.195973],[121.440358,23.272096],[121.479558,23.3223],[121.497788,23.419789],[121.521497,23.483198],[121.523078,23.538708],[121.587778,23.76102],[121.621604,23.92075],[121.659381,24.006893],[121.639992,24.064276],[121.643838,24.097713],[121.678085,24.133906],[121.689044,24.174401],[121.809172,24.339055],[121.826717,24.423579],[121.867498,24.478978],[121.885464,24.529677],[121.892524,24.617912],[121.862598,24.671515],[121.837993,24.76015],[121.845053,24.836269],[121.932883,24.938645],[122.012178,25.001469],[121.980776,25.03079],[121.947425,25.031955],[121.917077,25.137908],[121.842155,25.135332],[121.782407,25.160425],[121.750531,25.160716],[121.707327,25.191493],[121.700319,25.226913],[121.655324,25.241859],[121.623026,25.294694],[121.584986,25.308926],[121.535038,25.307515],[121.444415,25.270624],[121.413487,25.238912],[121.371864,25.159885],[121.319281,25.140691],[121.209322,25.127104],[121.133135,25.078728],[121.102102,25.075153],[121.024704,25.040479],[121.009688,24.993649],[120.960899,24.940227],[120.908475,24.852012],[120.892299,24.767526],[120.823753,24.688321],[120.762371,24.658335],[120.688661,24.600678],[120.64277,24.490172],[120.589187,24.432354],[120.546299,24.370413],[120.521009,24.312038],[120.470534,24.24259],[120.451461,24.182691],[120.392029,24.11824],[120.316158,23.984881],[120.278276,23.927798],[120.245768,23.840553],[120.175377,23.807385],[120.102773,23.700981],[120.094817,23.587466],[120.121741,23.504664],[120.107831,23.341264],[120.081434,23.29191],[120.018947,23.073115],[120.029537,23.048623],[120.131382,23.002118],[120.149138,22.896715],[120.200403,22.721101],[120.274272,22.560181],[120.297191,22.531315],[120.443558,22.441245]]],[[[124.542984,25.903911],[124.586346,25.913777],[124.572805,25.93974],[124.541825,25.931031],[124.542984,25.903911]]],[[[123.445286,25.725966],[123.472104,25.713024],[123.508933,25.723237],[123.514834,25.751226],[123.483063,25.768587],[123.444496,25.746514],[123.445286,25.725966]]],[[[119.64597,23.55091],[119.701081,23.550657],[119.678057,23.600041],[119.610089,23.603953],[119.594388,23.577245],[119.566306,23.584732],[119.562565,23.530377],[119.573788,23.505885],[119.609141,23.503864],[119.64597,23.55091]]],[[[123.667207,25.914066],[123.707092,25.916873],[123.678008,25.938667],[123.667207,25.914066]]],[[[119.506031,23.625567],[119.505241,23.575814],[119.472416,23.557136],[119.523207,23.563699],[119.525578,23.624895],[119.506031,23.625567]]],[[[119.49739,23.386683],[119.495125,23.350156],[119.516885,23.349903],[119.49739,23.386683]]],[[[119.557454,23.666474],[119.604083,23.616989],[119.615516,23.660925],[119.586485,23.675974],[119.557454,23.666474]]],[[[121.46823,22.676644],[121.476502,22.64166],[121.513541,22.631833],[121.5147,22.67639],[121.46823,22.676644]]],[[[121.510538,22.087185],[121.507693,22.048523],[121.534089,22.022146],[121.594522,21.995382],[121.604586,22.022699],[121.575028,22.037122],[121.575607,22.084421],[121.510538,22.087185]]],[[[122.097533,25.500168],[122.093581,25.47183],[122.124825,25.475932],[122.097533,25.500168]]],[[[119.421467,23.216684],[119.421309,23.18935],[119.453396,23.217697],[119.421467,23.216684]]],[[[120.355042,22.327259],[120.395454,22.342287],[120.383072,22.355573],[120.355042,22.327259]]]]}}]}
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router'
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* @LastEditors: 张宁
|
||||
* @LastEditTime: 2024-05-21 15:07:36
|
||||
*/
|
||||
import axios from "axios";
|
||||
import type { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import { StorageEnum, RequestEnum } from "@/enums";
|
||||
import { getLocalStorage,getToken } from "@/utils";
|
||||
|
||||
import UtilVar from "../config/UtilVar";
|
||||
let baseUrl = UtilVar.baseUrl;
|
||||
const CancelToken = axios.CancelToken;
|
||||
|
||||
export { baseUrl };
|
||||
// axios.defaults.withCredentials = true;
|
||||
// 添加请求拦截器
|
||||
axios.interceptors.request.use(
|
||||
function (config: AxiosRequestConfig): any {
|
||||
// 在发送请求之前做些什么 传token
|
||||
// let token: any = getLocalStorage(StorageEnum.GB_TOKEN_STORE);
|
||||
let token: any = getToken();
|
||||
if (token) {
|
||||
// @ts-ignore
|
||||
// config.headers.common[RequestEnum.GB_TOKEN_KEY] = token;
|
||||
config.headers['Authorization'] = token
|
||||
}
|
||||
// @ts-ignore
|
||||
config.headers["Content-Type"] = "application/json;charset=utf-8";
|
||||
|
||||
return config;
|
||||
},
|
||||
function (error: any) {
|
||||
// 对请求错误做些什么
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export type Params = { [key: string]: string | number };
|
||||
export type FileConfig = {
|
||||
setCancel?: Function;
|
||||
onProgress?: Function;
|
||||
[key: string]: any;
|
||||
};
|
||||
/**
|
||||
* @响应拦截
|
||||
*/
|
||||
axios.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
// console.log("response", response);
|
||||
if (response.status !== 200) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
/**
|
||||
* @code 登录过期 token验证失败 根据后端调
|
||||
*/
|
||||
if (response.data.code == UtilVar.code) {
|
||||
// router.push("/login")
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
console.log("error", error);
|
||||
let err = {
|
||||
success: false,
|
||||
msg: "未知异常,请联系管理员!",
|
||||
code: 400,
|
||||
};
|
||||
if (JSON.stringify(error).indexOf("Network Error") != -1) {
|
||||
err.msg = "网络错误或服务错误!";
|
||||
}
|
||||
if (error.message == "canceled") {
|
||||
err.msg = "取消请求";
|
||||
err.code = 488;
|
||||
}
|
||||
// console.log(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
//判断是否是加密参数,是的话处理
|
||||
let isEncryptionParam = (params: Params) => {
|
||||
return params;
|
||||
};
|
||||
/**
|
||||
* @description: get 请求方法
|
||||
* @param {string} url 请求地址
|
||||
* @param {Params} params 请求参数
|
||||
* @return {*}
|
||||
*/
|
||||
export const GET = async (url: string, params: Params): Promise<any> => {
|
||||
try {
|
||||
params = isEncryptionParam(params);
|
||||
const data = await axios.get(`${baseUrl}${url}`, {
|
||||
params: params,
|
||||
});
|
||||
return data.data;
|
||||
} catch (error: any) {
|
||||
return Promise.reject(error.msg);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description: post请求方法
|
||||
* @param {any} url
|
||||
* @param {any} params
|
||||
* @return {any}
|
||||
*/
|
||||
export const POST = async (url: string, params: Params): Promise<any> => {
|
||||
try {
|
||||
params = isEncryptionParam(params);
|
||||
const data = await axios.post(`${baseUrl}${url}`, params);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description: 没有基地址 访问根目录下文件
|
||||
* @param {string} url
|
||||
* @param {Params} params
|
||||
* @return {*}
|
||||
*/
|
||||
export const GETNOBASE = async (url: string, params?: Params): Promise<any> => {
|
||||
try {
|
||||
const data = await axios.get(url, {
|
||||
params: params,
|
||||
});
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 定义文件类型提交方法
|
||||
interface fileconfigs {
|
||||
[headers: string]: {
|
||||
"Content-Type": string;
|
||||
};
|
||||
}
|
||||
let configs: fileconfigs = {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
};
|
||||
/**
|
||||
* @description: @文件类型提交方法
|
||||
* @param {string} url
|
||||
* @param {Params} params
|
||||
* @param {FileConfig} config
|
||||
* @return {*}
|
||||
*/
|
||||
export const FILEPOST = async (url: string, params: Params, config: FileConfig = {}): Promise<any> => {
|
||||
try {
|
||||
const data = await axios.post(`${baseUrl}${url}`, params, {
|
||||
...configs,
|
||||
cancelToken: new CancelToken(function executor(c: any) {
|
||||
config.setCancel && config.setCancel(c);
|
||||
}),
|
||||
// 上传进度
|
||||
onUploadProgress: (e: any) => {
|
||||
if (e.total > 0) {
|
||||
e.percent = (e.loaded / e.total) * 100;
|
||||
}
|
||||
config.onProgress && config.onProgress(e);
|
||||
},
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载文档流
|
||||
* @param {config.responseType} 下载文件流根据后端 配置 arraybuffer || blod
|
||||
*/
|
||||
export const FILE = async (config: FileConfig = {}) => {
|
||||
try {
|
||||
const data = await axios({
|
||||
method: config.method || "get",
|
||||
url: `${baseUrl}${config.url}`,
|
||||
data: config.body || {},
|
||||
params: config.param || {},
|
||||
responseType: config.responseType || "blod",
|
||||
onDownloadProgress: (e: any) => {
|
||||
// console.log(e,e.currentTarget)
|
||||
// if (e.currentTarget.response.size > 0) {
|
||||
// e.percent = e.loaded / e.currentTarget.response.size * 100;
|
||||
// }
|
||||
// event.srcElement.getResponseHeader('content-length')
|
||||
config.onProgress && config.onProgress(e);
|
||||
},
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const PUT = async (url: string, params: Params) => {
|
||||
try {
|
||||
params = isEncryptionParam(params);
|
||||
const data = await axios.put(`${baseUrl}${url}`, params);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
export const DELETE = async (url: string, params: Params) => {
|
||||
// console.log(params)
|
||||
try {
|
||||
params = isEncryptionParam(params);
|
||||
const data = await axios.delete(`${baseUrl}${url}`, { data: params });
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// switch (error.response?.status) {
|
||||
// case 400:
|
||||
// error.message = '请求错误(400)';
|
||||
// break;
|
||||
// case 401:
|
||||
// error.message = '未授权(401)';
|
||||
// break;
|
||||
// case 403:
|
||||
// error.message = '拒绝访问(403)';
|
||||
// break;
|
||||
// case 404:
|
||||
// error.message = '请求出错(404)';
|
||||
// break;
|
||||
// case 408:
|
||||
// error.message = '请求超时(408)';
|
||||
// break;
|
||||
// case 500:
|
||||
// error.message = '服务器错误(500)';
|
||||
// break;
|
||||
// case 501:
|
||||
// error.message = '服务未实现(501)';
|
||||
// break;
|
||||
// case 502:
|
||||
// error.message = '网络错误(502)';
|
||||
// break;
|
||||
// case 503:
|
||||
// error.message = '服务不可用(503)';
|
||||
// break;
|
||||
// case 504:
|
||||
// error.message = '网络超时(504)';
|
||||
// break;
|
||||
// case 505:
|
||||
// error.message = 'HTTP版本不受支持(505)';
|
||||
// break;
|
||||
// default:
|
||||
// error.message = `连接出错(${error.response?.status})!`;
|
||||
// }
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* @Author: daidai
|
||||
* @Date: 2021-12-23 11:18:37
|
||||
|
||||
* @LastEditTime: 2024-03-28 16:07:20
|
||||
* @FilePath: \web-pc-svn\src\api\modules\index.js
|
||||
*/
|
||||
|
||||
import {GETNOBASE} from "./api";
|
||||
export * from "./modules/index"
|
||||
export * from "./modules/test"
|
||||
export {GETNOBASE}
|
|
@ -0,0 +1,54 @@
|
|||
import {GET,POST,FILE,FILEPOST,PUT,GETNOBASE} from "../api";
|
||||
const indexUrl= {
|
||||
'leftTop':'/bigscreen/countDeviceNum',//左上
|
||||
'leftCenter':'/bigscreen/countUserNum',//左中
|
||||
"centerMap":"/bigscreen/centerMap",
|
||||
"centerBottom":"/bigscreen/installationPlan",
|
||||
|
||||
'leftBottom':"/bigscreen/leftBottom", //坐下
|
||||
'rightTop':"/bigscreen/alarmNum", //报警次数
|
||||
'rightBottom':'/bigscreen/rightBottom',//右下
|
||||
'rightCenter':'/bigscreen/ranking',// 报警排名
|
||||
}
|
||||
|
||||
export default indexUrl
|
||||
|
||||
/**左上--设备内总览 */
|
||||
export const countDeviceNum=(param:any={})=>{
|
||||
return GET(indexUrl.leftTop,param)
|
||||
}
|
||||
|
||||
/**左中--用户总览 */
|
||||
export const countUserNum=(param:any={})=>{
|
||||
return GET(indexUrl.leftCenter,param)
|
||||
}
|
||||
|
||||
/**左下--设备提醒 */
|
||||
export const leftBottom=(param:any={})=>{
|
||||
return GET(indexUrl.leftBottom,param)
|
||||
}
|
||||
|
||||
/**中上--地图 */
|
||||
export const centerMap=(param:any={})=>{
|
||||
return GET(indexUrl.centerMap,param)
|
||||
}
|
||||
|
||||
/**中下--安装计划 */
|
||||
export const installationPlan=(param:any={})=>{
|
||||
return GET(indexUrl.centerBottom,param)
|
||||
}
|
||||
|
||||
/**右上--报警次数 */
|
||||
export const alarmNum=(param:any={})=>{
|
||||
return GET(indexUrl.rightTop,param)
|
||||
}
|
||||
|
||||
/**右中--报警排名 */
|
||||
export const ranking=(param:any={})=>{
|
||||
return GET(indexUrl.rightCenter,param)
|
||||
}
|
||||
|
||||
/**右下--设备状态 */
|
||||
export const rightBottom=(param:any={})=>{
|
||||
return GET(indexUrl.rightBottom,param)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import {GET,POST,FILE,FILEPOST,PUT,GETNOBASE} from "../api";
|
||||
export const testGet=(param:any={})=>{
|
||||
return GET('/api/TeacherManagement/AdminPCIndex',param)
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
html,body{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
#app{
|
||||
.content_wrap{
|
||||
color: #d3d6dd;
|
||||
}
|
||||
}
|
||||
|
||||
html .el-message {
|
||||
--yh-bg-color-container:#242424;
|
||||
--yh-shadow-3: 0 16px 24px rgba(0, 0, 0, .14), 0 6px 30px rgba(0, 0, 0, 12%), 0 8px 10px rgba(0, 0, 0, 20%);
|
||||
--yh-shadow-inset-top: inset 0 .5px 0 #5e5e5e;
|
||||
--yh-shadow-inset-right: inset .5px 0 0 #5e5e5e;
|
||||
--yh-shadow-inset-bottom: inset 0 -.5px 0 #5e5e5e;
|
||||
--yh-shadow-inset-left: inset -.5px 0 0 #5e5e5e;
|
||||
--yh-text-color-primary:rgba(255, 255, 255, .9);
|
||||
--yh-brand-color: #0052d9;
|
||||
--yh-success-color: #059465;
|
||||
--yh-error-color: #c64751;
|
||||
--yh-warning-color: #cf6e2d;
|
||||
|
||||
background-color: var(--yh-bg-color-container) ;
|
||||
box-shadow: var(--yh-shadow-3), var(--yh-shadow-inset-top),
|
||||
var(--yh-shadow-inset-right), var(--yh-shadow-inset-bottom),
|
||||
var(--yh-shadow-inset-left);
|
||||
border: none ;
|
||||
color: var(--yh-text-color-primary) ;
|
||||
margin-top: 90px;
|
||||
.el-message__icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&.el-message--info .el-message__icon {
|
||||
color: var(--yh-brand-color);
|
||||
}
|
||||
|
||||
&.el-message--success .el-message__icon {
|
||||
color: var(--yh-success-color);
|
||||
}
|
||||
|
||||
&.el-message--warning .el-message__icon {
|
||||
color: var(--yh-warning-color);
|
||||
}
|
||||
|
||||
&.el-message--error .el-message__icon {
|
||||
color: var(--yh-error-color);
|
||||
}
|
||||
|
||||
.el-message__content {
|
||||
color: var(--yh-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.beautify-scroll-def {
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
//滚动条的设置
|
||||
background-color: rgba(14, 59, 150, 0);
|
||||
background-clip: padding-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
//滚动条的设置
|
||||
background-color: rgba(14, 59, 150, 0.5);
|
||||
background-clip: padding-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
//滚动条凹槽的颜色,还可以设置边框属性
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
//滚动条的宽度
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(14, 59, 150, .8);
|
||||
}
|
||||
}
|
||||
|
||||
.text-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
min-height: calc(100% - 60px);
|
||||
justify-content: space-between;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
/* @tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities; */
|
|
@ -0,0 +1 @@
|
|||
$primary-color: #1890ff;
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 323 B |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 289 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,3 @@
|
|||
import MessageContent from './index.vue';
|
||||
|
||||
export default MessageContent ;
|
|
@ -0,0 +1,8 @@
|
|||
<!-- eslint-disable vue/valid-template-root -->
|
||||
<template></template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$message'] = ElMessage
|
||||
</script>
|
|
@ -0,0 +1,146 @@
|
|||
<script lang="ts">
|
||||
export type { CountUp as ICountUp, CountUpOptions } from 'countup.js'
|
||||
export default {
|
||||
name: 'CountUp'
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { CountUp } from 'countup.js'
|
||||
import type { CountUpOptions } from 'countup.js'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 结束数值
|
||||
endVal: number | string
|
||||
// 开始数值
|
||||
startVal?: number | string
|
||||
// 动画时长,单位 s
|
||||
duration?: number | string
|
||||
// 是否自动计数
|
||||
autoplay?: boolean
|
||||
// 循环次数,有限次数 / 无限循环
|
||||
loop?: boolean | number | string
|
||||
// 延时,单位 s
|
||||
delay?: number
|
||||
// countup 配置项
|
||||
options?: CountUpOptions
|
||||
}>(),
|
||||
{
|
||||
startVal: 0,
|
||||
duration: 2.5,
|
||||
autoplay: true,
|
||||
loop: false,
|
||||
delay: 0,
|
||||
options: undefined
|
||||
}
|
||||
)
|
||||
const emits = defineEmits<{
|
||||
// countup init complete
|
||||
(event: 'init', countup: CountUp): void
|
||||
// count complete
|
||||
(event: 'finished'): void
|
||||
}>()
|
||||
|
||||
let elRef = ref<HTMLElement>()
|
||||
let countUp = ref<CountUp>()
|
||||
|
||||
const initCountUp = () => {
|
||||
if (!elRef.value) return
|
||||
const startVal = Number(props.startVal)
|
||||
const endVal = Number(props.endVal)
|
||||
const duration = Number(props.duration)
|
||||
countUp.value = new CountUp(elRef.value, endVal, {
|
||||
startVal,
|
||||
duration,
|
||||
...props.options
|
||||
})
|
||||
if (countUp.value.error) {
|
||||
console.error(countUp.value.error)
|
||||
return
|
||||
}
|
||||
emits('init', countUp.value)
|
||||
}
|
||||
|
||||
const startAnim = (cb?: () => void) => {
|
||||
countUp.value?.start(cb)
|
||||
}
|
||||
|
||||
// endVal change & autoplay: true, restart animate
|
||||
watch(
|
||||
() => props.endVal,
|
||||
(value) => {
|
||||
if (props.autoplay) {
|
||||
countUp.value?.update(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// loop animation
|
||||
const finished = ref(false)
|
||||
let loopCount = 0
|
||||
const loopAnim = () => {
|
||||
loopCount++
|
||||
startAnim(() => {
|
||||
const isTruely = typeof props.loop === 'boolean' && props.loop
|
||||
if (isTruely || props.loop > loopCount) {
|
||||
delay(() => {
|
||||
countUp.value?.reset()
|
||||
loopAnim()
|
||||
}, props.delay)
|
||||
} else {
|
||||
finished.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
watch(finished, (flag) => {
|
||||
if (flag) {
|
||||
emits('finished')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initCountUp()
|
||||
if (props.autoplay) {
|
||||
loopAnim()
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(dalayRafId)
|
||||
countUp.value?.reset()
|
||||
})
|
||||
|
||||
let dalayRafId: number
|
||||
// delay to execute callback function
|
||||
const delay = (cb: () => unknown, seconds = 1) => {
|
||||
let startTime: number
|
||||
function count(timestamp: number) {
|
||||
if (!startTime) startTime = timestamp
|
||||
const diff = timestamp - startTime
|
||||
if (diff < seconds * 1000) {
|
||||
dalayRafId = requestAnimationFrame(count)
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
dalayRafId = requestAnimationFrame(count)
|
||||
}
|
||||
|
||||
const restart = () => {
|
||||
initCountUp()
|
||||
startAnim()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init: initCountUp,
|
||||
restart
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="countup-wrap">
|
||||
<slot name="prefix"></slot>
|
||||
<span ref="elRef"> </span>
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,3 @@
|
|||
import CountUp from "./count-up.vue"
|
||||
export default CountUp
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref ,onBeforeUpdate, nextTick} from "vue";
|
||||
import merge from "lodash/merge";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import type { PropType } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
color: {
|
||||
type: Array as unknown as PropType<[string, string]>,
|
||||
default: () => [],
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "transparent",
|
||||
},
|
||||
});
|
||||
const defaultColor = ["#6586ec", "#2cf7fe"];
|
||||
const domRef = ref(null);
|
||||
const { width, height } = useElementSize(domRef,{width:0,height:0}, { box: 'border-box' });
|
||||
const mergedColor = computed<[string, string]>(() => {
|
||||
return merge(defaultColor, props.color);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-13 dv-border-box" ref="domRef">
|
||||
<svg :width="width" :height="height" class="dv-border-svg-container">
|
||||
<path
|
||||
:fill="backgroundColor"
|
||||
:stroke="mergedColor[0]"
|
||||
:d="`
|
||||
M 5 20 L 5 10 L 12 3 L 60 3 L 68 10
|
||||
L ${width - 20} 10 L ${width - 5} 25
|
||||
L ${width - 5} ${height - 5} L 20 ${height - 5}
|
||||
L 5 ${height - 20} L 5 20
|
||||
`"
|
||||
/>
|
||||
|
||||
<path
|
||||
fill="transparent"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="10, 5"
|
||||
:stroke="mergedColor[0]"
|
||||
:d="`M 16 9 L 61 9`"
|
||||
/>
|
||||
|
||||
<path
|
||||
fill="transparent"
|
||||
stroke="{mergedColor[1]}"
|
||||
:d="`M 5 20 L 5 10 L 12 3 L 60 3 L 68 10`"
|
||||
/>
|
||||
|
||||
<path
|
||||
fill="transparent"
|
||||
:stroke="mergedColor[1]"
|
||||
:d="`M ${width - 5} ${height - 30} L ${width - 5} ${height - 5} L ${
|
||||
width - 30
|
||||
} ${height - 5}`"
|
||||
/>
|
||||
</svg>
|
||||
<div class="dv-border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dv-border-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
display: block;
|
||||
}
|
||||
.dv-border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,3 @@
|
|||
import BorderBox13 from "./border-box-13.vue"
|
||||
|
||||
export default BorderBox13
|
|
@ -0,0 +1,186 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import type { DefaultConfigType } from "./index.d";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import merge from "lodash/merge";
|
||||
const mergedConfig = ref<any>(null);
|
||||
const capsuleLength = ref<any>([]);
|
||||
const capsuleValue = ref<any>([]);
|
||||
const labelData = ref<any>([]);
|
||||
// const labelDataLength = ref<any>([]);
|
||||
|
||||
const defaultConfig = reactive<DefaultConfigType>({
|
||||
// Colors (hex|rgb|rgba|color keywords) ['#000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)', 'red']
|
||||
colors: [
|
||||
"#37a2da",
|
||||
"#32c5e9",
|
||||
"#67e0e3",
|
||||
"#9fe6b8",
|
||||
"#ffdb5c",
|
||||
"#ff9f7f",
|
||||
"#fb7293",
|
||||
],
|
||||
unit: "",
|
||||
showValue: false, // Show item value
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
config: object | any;
|
||||
data: Array<{
|
||||
name: string;
|
||||
value: string | number;
|
||||
}>;
|
||||
}>(),
|
||||
{
|
||||
config: () => { },
|
||||
data: () => [],
|
||||
}
|
||||
);
|
||||
const calcData = () => {
|
||||
mergeConfig();
|
||||
calcCapsuleLengthAndLabelData();
|
||||
};
|
||||
const mergeConfig = () => {
|
||||
mergedConfig.value = merge(cloneDeep(defaultConfig), props.config || {});
|
||||
};
|
||||
const calcCapsuleLengthAndLabelData = () => {
|
||||
if (!props.data.length) return;
|
||||
const newcapsuleValue = props.data.map((item: any) => item.value);
|
||||
const maxValue = Math.max(...newcapsuleValue);
|
||||
capsuleValue.value = newcapsuleValue;
|
||||
capsuleLength.value = newcapsuleValue.map((v: any) =>
|
||||
maxValue ? v / maxValue : 0
|
||||
);
|
||||
const oneFifth = maxValue / 5;
|
||||
const newlabelData = Array.from(
|
||||
new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth)))
|
||||
);
|
||||
labelData.value = newlabelData;
|
||||
// labelDataLength.value = Array.from(newlabelData).map((v) =>
|
||||
// maxValue ? v / maxValue : 0
|
||||
// );
|
||||
// console.log(labelDataLength.value);
|
||||
};
|
||||
watch(
|
||||
() => props.data,
|
||||
(newval: any) => {
|
||||
calcData();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.config,
|
||||
(newval: any) => {
|
||||
calcData();
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
calcData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-capsule-chart">
|
||||
<template v-if="mergedConfig">
|
||||
<div class="label-column">
|
||||
<div v-for="item in data" :key="item.name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div> </div>
|
||||
</div>
|
||||
<div class="capsule-container">
|
||||
<div class="capsule-item" v-for="(capsule, index) in capsuleLength" :key="index">
|
||||
<div class="capsule-item-column" :style="`width: ${capsule * 100}%; background-color: ${mergedConfig.colors[index % mergedConfig.colors.length]
|
||||
};`">
|
||||
<div v-if="mergedConfig.showValue" class="capsule-item-value">
|
||||
{{ capsuleValue[index] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unit-label">
|
||||
<div v-for="(label, index) in labelData" :key="label + index">
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unit-text" v-if="mergedConfig.unit">
|
||||
{{ mergedConfig.unit }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dv-capsule-chart {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
|
||||
.label-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
|
||||
div {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.capsule-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.capsule-item {
|
||||
box-shadow: 0 0 3px #999;
|
||||
height: 10px;
|
||||
margin: 5px 0px;
|
||||
border-radius: 5px;
|
||||
|
||||
.capsule-item-column {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
margin-top: 1px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.capsule-item-value {
|
||||
font-size: 12px;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-label {
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unit-text {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,6 @@
|
|||
export interface DefaultConfigType {
|
||||
|
||||
colors: Array<String>;
|
||||
unit:string,
|
||||
showValue:Boolean
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import CapsuleChart from "./capsule-chart.vue"
|
||||
export * from "./index.d"
|
||||
export default CapsuleChart
|
|
@ -0,0 +1,9 @@
|
|||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -0,0 +1,2 @@
|
|||
import EmptyCom from "./empty-com.vue"
|
||||
export default EmptyCom
|
|
@ -0,0 +1,2 @@
|
|||
import ItemWrap from "./item-wrap.vue"
|
||||
export default ItemWrap
|
|
@ -0,0 +1,81 @@
|
|||
<script setup lang="ts">
|
||||
import BorderBox13 from "@/components/datav/border-box-13";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 标题
|
||||
title: number | string;
|
||||
}>(),
|
||||
{
|
||||
title: "",
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BorderBox13>
|
||||
<div class="item_title" v-if="title !== ''">
|
||||
<div class="zuo"></div>
|
||||
<span class="title-inner"> {{ title }} </span>
|
||||
<div class="you"></div>
|
||||
</div>
|
||||
<div
|
||||
:class="title !== '' ? 'item_title_content' : 'item_title_content_def'"
|
||||
>
|
||||
<slot></slot></div
|
||||
></BorderBox13>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$item-title-height: 38px;
|
||||
$item_title_content-height: calc(100% - 38px);
|
||||
|
||||
.item_title {
|
||||
height: $item-title-height;
|
||||
line-height: $item-title-height;
|
||||
width: 100%;
|
||||
color: #31abe3;
|
||||
text-align: center;
|
||||
// background: linear-gradient(to right, transparent, #0f0756, transparent);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.zuo,
|
||||
.you {
|
||||
width: 58px;
|
||||
height: 14px;
|
||||
background-image: url("@/assets/img/titles/zuo.png");
|
||||
}
|
||||
|
||||
.you {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.title-inner {
|
||||
font-weight: 900;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(
|
||||
92deg,
|
||||
#0072ff 0%,
|
||||
#00eaff 48.8525390625%,
|
||||
#01aaff 100%
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.dv-border-box-content) {
|
||||
box-sizing: border-box;
|
||||
padding: 6px 16px 0px;
|
||||
}
|
||||
|
||||
.item_title_content {
|
||||
height: $item_title_content-height;
|
||||
}
|
||||
|
||||
.item_title_content_def {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,3 @@
|
|||
import ScaleScreen from './scale-screen.vue'
|
||||
|
||||
export default ScaleScreen
|
|
@ -0,0 +1,246 @@
|
|||
<template>
|
||||
<section
|
||||
:style="{ ...styles.box, ...boxStyle }"
|
||||
class="v-screen-box"
|
||||
ref="box"
|
||||
>
|
||||
<div
|
||||
:style="{ ...styles.wrapper, ...wrapperStyle }"
|
||||
class="screen-wrapper"
|
||||
ref="screenWrapper"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||
import type { CSSProperties, PropType } from "vue";
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} fn
|
||||
* @param {number} delay
|
||||
* @returns {() => void}
|
||||
*/
|
||||
function debounce(fn: Function, delay: number): () => void {
|
||||
// let timer: NodeJS.Timer;
|
||||
let timer: any;
|
||||
return function (...args: any[]): void {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(
|
||||
() => {
|
||||
typeof fn === "function" && fn.apply(null, args);
|
||||
clearTimeout(timer);
|
||||
},
|
||||
delay > 0 ? delay : 100
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface IState {
|
||||
originalWidth: string | number;
|
||||
originalHeight: string | number;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
observer: null | MutationObserver;
|
||||
}
|
||||
type IAutoScale =
|
||||
| boolean
|
||||
| {
|
||||
x?: boolean;
|
||||
y?: boolean;
|
||||
};
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 1920,
|
||||
},
|
||||
height: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 1080,
|
||||
},
|
||||
fullScreen: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
autoScale: {
|
||||
type: [Object, Boolean] as PropType<IAutoScale>,
|
||||
default: true,
|
||||
},
|
||||
delay: {
|
||||
type: Number as PropType<number>,
|
||||
default: 500,
|
||||
},
|
||||
boxStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({}),
|
||||
},
|
||||
wrapperStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive<IState>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
originalWidth: 0,
|
||||
originalHeight: 0,
|
||||
observer: null,
|
||||
});
|
||||
|
||||
const styles: Record<string, CSSProperties> = {
|
||||
box: {
|
||||
overflow: "hidden",
|
||||
backgroundSize: `100% 100%`,
|
||||
background: `#000`,
|
||||
width: `100vw`,
|
||||
height: `100vh`,
|
||||
},
|
||||
wrapper: {
|
||||
transitionProperty: `all`,
|
||||
transitionTimingFunction: `cubic-bezier(0.4, 0, 0.2, 1)`,
|
||||
transitionDuration: `500ms`,
|
||||
position: `relative`,
|
||||
overflow: `hidden`,
|
||||
zIndex: 100,
|
||||
transformOrigin: `left top`,
|
||||
},
|
||||
};
|
||||
|
||||
const screenWrapper = ref<HTMLElement>();
|
||||
const box = ref<HTMLElement>();
|
||||
|
||||
watch(
|
||||
() => props.autoScale,
|
||||
async (newVal: any) => {
|
||||
if (newVal) {
|
||||
onResize();
|
||||
addListener();
|
||||
} else {
|
||||
clearListener();
|
||||
clearScreenWrapperStyle();
|
||||
}
|
||||
}
|
||||
);
|
||||
/**
|
||||
* 初始化大屏容器宽高
|
||||
*/
|
||||
const initSize = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
box.value!.scrollLeft = 0;
|
||||
box.value!.scrollTop = 0;
|
||||
nextTick(() => {
|
||||
// region 获取大屏真实尺寸
|
||||
if (props.width && props.height) {
|
||||
state.width = props.width;
|
||||
state.height = props.height;
|
||||
} else {
|
||||
state.width = screenWrapper.value?.clientWidth;
|
||||
state.height = screenWrapper.value?.clientHeight;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 获取画布尺寸
|
||||
if (!state.originalHeight || !state.originalWidth) {
|
||||
state.originalWidth = window.screen.width;
|
||||
state.originalHeight = window.screen.height;
|
||||
}
|
||||
// endregion
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新大屏容器宽高
|
||||
*/
|
||||
const updateSize = () => {
|
||||
if (state.width && state.height) {
|
||||
screenWrapper.value!.style.width = `${state.width}px`;
|
||||
screenWrapper.value!.style.height = `${state.height}px`;
|
||||
} else {
|
||||
screenWrapper.value!.style.width = `${state.originalWidth}px`;
|
||||
screenWrapper.value!.style.height = `${state.originalHeight}px`;
|
||||
}
|
||||
};
|
||||
const clearScreenWrapperStyle = () => {
|
||||
screenWrapper.value!.style.transform = "";
|
||||
screenWrapper.value!.style.margin = "";
|
||||
};
|
||||
const autoScale = (scale: number) => {
|
||||
if (!props.autoScale) {
|
||||
return;
|
||||
}
|
||||
const domWidth = screenWrapper.value!.clientWidth;
|
||||
const domHeight = screenWrapper.value!.clientHeight;
|
||||
const currentWidth = document.body.clientWidth;
|
||||
const currentHeight = document.body.clientHeight;
|
||||
screenWrapper.value!.style.transform = `scale(${scale},${scale})`;
|
||||
let mx = Math.max((currentWidth - domWidth * scale) / 2, 0);
|
||||
let my = Math.max((currentHeight - domHeight * scale) / 2, 0);
|
||||
if (typeof props.autoScale === "object") {
|
||||
!props.autoScale.x && (mx = 0);
|
||||
!props.autoScale.y && (my = 0);
|
||||
}
|
||||
screenWrapper.value!.style.margin = `${my}px ${mx}px`;
|
||||
};
|
||||
const updateScale = () => {
|
||||
// 获取真实视口尺寸
|
||||
const currentWidth = document.body.clientWidth;
|
||||
const currentHeight = document.body.clientHeight;
|
||||
// 获取大屏最终的宽高
|
||||
const realWidth = state.width || state.originalWidth;
|
||||
const realHeight = state.height || state.originalHeight;
|
||||
// 计算缩放比例
|
||||
const widthScale = currentWidth / +realWidth;
|
||||
const heightScale = currentHeight / +realHeight;
|
||||
// 若要铺满全屏,则按照各自比例缩放
|
||||
if (props.fullScreen) {
|
||||
screenWrapper.value!.style.transform = `scale(${widthScale},${heightScale})`;
|
||||
return false;
|
||||
}
|
||||
// 按照宽高最小比例进行缩放
|
||||
const scale = Math.min(widthScale, heightScale);
|
||||
autoScale(scale);
|
||||
};
|
||||
|
||||
const onResize = debounce(async () => {
|
||||
await initSize();
|
||||
updateSize();
|
||||
updateScale();
|
||||
}, props.delay);
|
||||
const initMutationObserver = () => {
|
||||
const observer = (state.observer = new MutationObserver(() => {
|
||||
onResize();
|
||||
}));
|
||||
observer.observe(screenWrapper.value!, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
attributeOldValue: true,
|
||||
});
|
||||
};
|
||||
|
||||
const clearListener = () => {
|
||||
window.removeEventListener("resize", onResize);
|
||||
// state.observer?.disconnect();
|
||||
};
|
||||
|
||||
const addListener = () => {
|
||||
window.addEventListener("resize", onResize);
|
||||
// initMutationObserver();
|
||||
};
|
||||
onMounted(() => {
|
||||
nextTick(async () => {
|
||||
await initSize();
|
||||
updateSize();
|
||||
updateScale();
|
||||
addListener();
|
||||
// initMutationObserver();
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
clearListener();
|
||||
// state.observer?.disconnect();
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,2 @@
|
|||
import SeamlessScroll from "./seamless-scroll.vue"
|
||||
export default SeamlessScroll
|
|
@ -0,0 +1,407 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
ref,
|
||||
watch,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import type { CSSProperties } from "vue";
|
||||
import throttle from "lodash/throttle";
|
||||
type propsType = {
|
||||
modelValue?: boolean;
|
||||
list: Array<any>;
|
||||
step?: number;
|
||||
limitScrollNum?: number;
|
||||
hover?: boolean;
|
||||
direction?: string;
|
||||
singleHeight?: number;
|
||||
singleWidth?: number;
|
||||
singleWaitTime?: number;
|
||||
isRemUnit?: boolean;
|
||||
isWatch?: boolean;
|
||||
delay?: number;
|
||||
ease?: any;
|
||||
count?: number;
|
||||
copyNum?: number;
|
||||
wheel?: boolean;
|
||||
singleLine?: boolean;
|
||||
};
|
||||
const props = withDefaults(defineProps<propsType>(), {
|
||||
// 是否开启自动滚动
|
||||
modelValue: true,
|
||||
// 原始数据列表
|
||||
list: () => [],
|
||||
// 步进速度,step 需是单步大小的约数
|
||||
step: 1,
|
||||
// 开启滚动的数据量
|
||||
limitScrollNum: 3,
|
||||
// 是否开启鼠标悬停
|
||||
hover: false,
|
||||
// 控制滚动方向
|
||||
direction: "up",
|
||||
// 单步运动停止的高度
|
||||
singleHeight: 0,
|
||||
// 单步运动停止的宽度
|
||||
singleWidth: 0,
|
||||
// 单步停止等待时间 (默认值 1000ms)
|
||||
singleWaitTime: 1000,
|
||||
// 是否开启 rem 度量
|
||||
isRemUnit: false,
|
||||
// 开启数据更新监听
|
||||
isWatch: true,
|
||||
// 动画时间
|
||||
delay: 0,
|
||||
// 动画方式
|
||||
ease: "ease-in",
|
||||
// 动画循环次数,-1 表示一直动画
|
||||
count: -1,
|
||||
// 拷贝几份滚动列表
|
||||
copyNum: 1,
|
||||
// 开启鼠标悬停时支持滚轮滚动
|
||||
wheel: false,
|
||||
// 启用单行滚动
|
||||
singleLine: false,
|
||||
});
|
||||
interface Emits {
|
||||
(event: "count", _count: number): void;
|
||||
(event: "stop", _count: number): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>();
|
||||
const scrollRef = ref(null);
|
||||
const slotListRef = ref<HTMLDivElement | null>(null);
|
||||
const realBoxRef = ref<HTMLDivElement | null>(null);
|
||||
const reqFrame = ref<number | null>(null);
|
||||
const singleWaitTimeout = ref<TimeProp | null>(null);
|
||||
const realBoxWidth = ref(0);
|
||||
const realBoxHeight = ref(0);
|
||||
const xPos = ref(0);
|
||||
const yPos = ref(0);
|
||||
const isHover = ref(false);
|
||||
const _count = ref(0);
|
||||
const isScroll = computed(() =>
|
||||
props.list ? props.list.length >= props.limitScrollNum : false
|
||||
);
|
||||
const realBoxStyle = computed(() => {
|
||||
return {
|
||||
width: realBoxWidth.value ? `${realBoxWidth.value}px` : "auto",
|
||||
transform: `translate(${xPos.value}px,${yPos.value}px)`,
|
||||
transition: `all ${
|
||||
typeof props.ease === "string"
|
||||
? props.ease
|
||||
: "cubic-bezier(" +
|
||||
props.ease.x1 +
|
||||
"," +
|
||||
props.ease.y1 +
|
||||
"," +
|
||||
props.ease.x2 +
|
||||
"," +
|
||||
props.ease.y2 +
|
||||
")"
|
||||
} ${props.delay}ms`,
|
||||
overflow: "hidden",
|
||||
display: props.singleLine ? "flex" : "block",
|
||||
};
|
||||
});
|
||||
const isHorizontal = computed(
|
||||
() => props.direction == "left" || props.direction == "right"
|
||||
);
|
||||
|
||||
function dataWarm(list: any) {
|
||||
if (list && typeof list !== "boolean" && list.length > 100) {
|
||||
console.warn(
|
||||
`数据达到了${list.length}条有点多哦~,可能会造成部分老旧浏览器卡顿。`
|
||||
);
|
||||
}
|
||||
}
|
||||
const floatStyle = computed<CSSProperties>(() => {
|
||||
return isHorizontal.value
|
||||
? {
|
||||
float: "left",
|
||||
overflow: "hidden",
|
||||
display: props.singleLine ? "flex" : "block",
|
||||
flexShrink: props.singleLine ? 0 : 1,
|
||||
}
|
||||
: { overflow: "hidden" };
|
||||
});
|
||||
const baseFontSize = computed(() => {
|
||||
return props.isRemUnit
|
||||
? parseInt(
|
||||
globalThis.window.getComputedStyle(
|
||||
globalThis.document.documentElement,
|
||||
null
|
||||
).fontSize
|
||||
)
|
||||
: 1;
|
||||
});
|
||||
const realSingleStopWidth = computed(
|
||||
() => props.singleWidth * baseFontSize.value
|
||||
);
|
||||
|
||||
const realSingleStopHeight = computed(
|
||||
() => props.singleHeight * baseFontSize.value
|
||||
);
|
||||
|
||||
const step = computed(() => {
|
||||
let singleStep: number;
|
||||
let _step = props.step;
|
||||
if (isHorizontal.value) {
|
||||
singleStep = realSingleStopWidth.value;
|
||||
} else {
|
||||
singleStep = realSingleStopHeight.value;
|
||||
}
|
||||
if (singleStep > 0 && singleStep % _step > 0) {
|
||||
console.error(
|
||||
"如果设置了单步滚动,step 需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~"
|
||||
);
|
||||
}
|
||||
return _step;
|
||||
});
|
||||
|
||||
const cancle = () => {
|
||||
cancelAnimationFrame(reqFrame.value as number);
|
||||
reqFrame.value = null;
|
||||
};
|
||||
const animation = (
|
||||
_direction: "up" | "down" | "left" | "right",
|
||||
_step: number,
|
||||
isWheel?: boolean
|
||||
) => {
|
||||
// console.log("animation",_direction,_step,isWheel);
|
||||
reqFrame.value = requestAnimationFrame(function () {
|
||||
const h = realBoxHeight.value / 2;
|
||||
const w = realBoxWidth.value / 2;
|
||||
if (_direction === "up") {
|
||||
if (Math.abs(yPos.value) >= h) {
|
||||
yPos.value = 0;
|
||||
_count.value += 1;
|
||||
emit("count", _count.value);
|
||||
}
|
||||
yPos.value -= _step;
|
||||
} else if (_direction === "down") {
|
||||
if (yPos.value >= 0) {
|
||||
yPos.value = h * -1;
|
||||
_count.value += 1;
|
||||
emit("count", _count.value);
|
||||
}
|
||||
yPos.value += _step;
|
||||
} else if (_direction === "left") {
|
||||
if (Math.abs(xPos.value) >= w) {
|
||||
xPos.value = 0;
|
||||
_count.value += 1;
|
||||
emit("count", _count.value);
|
||||
}
|
||||
xPos.value -= _step;
|
||||
} else if (_direction === "right") {
|
||||
if (xPos.value >= 0) {
|
||||
xPos.value = w * -1;
|
||||
_count.value += 1;
|
||||
emit("count", _count.value);
|
||||
}
|
||||
xPos.value += _step;
|
||||
}
|
||||
if (isWheel) {
|
||||
return;
|
||||
}
|
||||
let { singleWaitTime } = props;
|
||||
if (singleWaitTimeout.value) {
|
||||
clearTimeout(singleWaitTimeout.value);
|
||||
}
|
||||
if (!!realSingleStopHeight.value) {
|
||||
if (Math.abs(yPos.value) % realSingleStopHeight.value < _step) {
|
||||
singleWaitTimeout.value = setTimeout(() => {
|
||||
move();
|
||||
}, singleWaitTime);
|
||||
} else {
|
||||
move();
|
||||
}
|
||||
} else if (!!realSingleStopWidth.value) {
|
||||
if (Math.abs(xPos.value) % realSingleStopWidth.value < _step) {
|
||||
singleWaitTimeout.value = setTimeout(() => {
|
||||
move();
|
||||
}, singleWaitTime);
|
||||
} else {
|
||||
move();
|
||||
}
|
||||
} else {
|
||||
move();
|
||||
}
|
||||
});
|
||||
};
|
||||
const move = () => {
|
||||
cancle();
|
||||
if (isHover.value || !isScroll.value || _count.value === props.count) {
|
||||
emit("stop", _count.value);
|
||||
_count.value = 0;
|
||||
return;
|
||||
}
|
||||
animation(
|
||||
props.direction as "up" | "down" | "left" | "right",
|
||||
step.value,
|
||||
false
|
||||
);
|
||||
};
|
||||
const initMove = () => {
|
||||
dataWarm(props.list);
|
||||
if (isHorizontal.value) {
|
||||
let slotListWidth = (slotListRef.value as HTMLDivElement).offsetWidth;
|
||||
slotListWidth = slotListWidth * 2 + 1;
|
||||
realBoxWidth.value = slotListWidth;
|
||||
}
|
||||
if (isScroll.value) {
|
||||
realBoxHeight.value = (realBoxRef.value as HTMLDivElement).offsetHeight;
|
||||
if (props.modelValue) {
|
||||
move();
|
||||
}
|
||||
} else {
|
||||
cancle();
|
||||
yPos.value = xPos.value = 0;
|
||||
}
|
||||
// console.log("initMove","isHorizontal",isHorizontal.value,"isScroll",isScroll.value,realBoxRef.value?.offsetHeight);
|
||||
};
|
||||
const startMove = () => {
|
||||
isHover.value = false;
|
||||
move();
|
||||
};
|
||||
|
||||
const stopMove = () => {
|
||||
isHover.value = true;
|
||||
if (singleWaitTimeout.value) {
|
||||
clearTimeout(singleWaitTimeout.value);
|
||||
}
|
||||
cancle();
|
||||
};
|
||||
|
||||
const hoverStop = computed(
|
||||
() => props.hover && props.modelValue && isScroll.value
|
||||
);
|
||||
const throttleFunc = throttle((e: WheelEvent) => {
|
||||
cancle();
|
||||
const singleHeight = !!realSingleStopHeight.value
|
||||
? realSingleStopHeight.value
|
||||
: 15;
|
||||
if (e.deltaY < 0) {
|
||||
animation("down", singleHeight, true);
|
||||
}
|
||||
if (e.deltaY > 0) {
|
||||
animation("up", singleHeight, true);
|
||||
}
|
||||
}, 30);
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
throttleFunc(e);
|
||||
};
|
||||
const reset = () => {
|
||||
cancle();
|
||||
isHover.value = false;
|
||||
initMove();
|
||||
};
|
||||
const Reset = () => {
|
||||
reset();
|
||||
};
|
||||
defineExpose({
|
||||
Reset,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.list,
|
||||
() => {
|
||||
if (props.isWatch) {
|
||||
nextTick(() => {
|
||||
reset();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
startMove();
|
||||
} else {
|
||||
stopMove();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.count,
|
||||
(newValue) => {
|
||||
if (newValue !== 0) {
|
||||
startMove();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
cancle();
|
||||
clearTimeout(singleWaitTimeout.value as unknown as number);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (isScroll.value) {
|
||||
initMove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="props.wheel && props.hover"
|
||||
ref="realBoxRef"
|
||||
:style="realBoxStyle"
|
||||
@mouseenter="
|
||||
() => {
|
||||
hoverStop && stopMove();
|
||||
}
|
||||
"
|
||||
@mouseleave="
|
||||
() => {
|
||||
hoverStop && startMove();
|
||||
}
|
||||
"
|
||||
@wheel="
|
||||
(e) => {
|
||||
hoverStop && onWheel(e);
|
||||
}
|
||||
"
|
||||
>
|
||||
<div ref="slotListRef" :style="floatStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div :style="floatStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
:style="realBoxStyle"
|
||||
ref="realBoxRef"
|
||||
@mouseenter="
|
||||
() => {
|
||||
hoverStop && stopMove();
|
||||
}
|
||||
"
|
||||
@mouseleave="
|
||||
() => {
|
||||
hoverStop && startMove();
|
||||
}
|
||||
"
|
||||
>
|
||||
<div ref="slotListRef" :style="floatStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div :style="floatStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -0,0 +1,32 @@
|
|||
interface UtilVarType {
|
||||
baseUrl:string,
|
||||
code:string|number,
|
||||
noContentCode:number,
|
||||
ENC:boolean,//是否进行加密
|
||||
}
|
||||
|
||||
const UtilVar:UtilVarType = {
|
||||
baseUrl:"http://api.nclg.yx.zheke.com",
|
||||
code:401, //登陆过期
|
||||
noContentCode:204, //请求成功但没有内容
|
||||
ENC:false,
|
||||
|
||||
}
|
||||
const runtimeType:any = {
|
||||
|
||||
production: () => {
|
||||
UtilVar.baseUrl= `http://api.nclg.yx.zheke.com`
|
||||
},
|
||||
//开发环境
|
||||
development: () => {
|
||||
UtilVar.baseUrl= `http://api.nclg.yx.zheke.com`
|
||||
|
||||
},
|
||||
hash:()=>{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// console.log(import.meta.env)
|
||||
runtimeType[import.meta.env.MODE]&&runtimeType[import.meta.env.MODE]()
|
||||
export default UtilVar
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./storage-enum"
|
||||
export * from "./request-enums"
|
|
@ -0,0 +1,12 @@
|
|||
export enum RequestEnum {
|
||||
// token key
|
||||
GB_TOKEN_KEY = 'auth-token',
|
||||
// 验签key
|
||||
GB_SIGN_KEY = "sign",
|
||||
// 时间戳 key
|
||||
GB_TIMESTAMP_KEY = "timestamp"
|
||||
}
|
||||
export enum ReqCodeEnum {
|
||||
Unauthorized = 401,// token过期
|
||||
Success = 200,//成功
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export enum StorageEnum {
|
||||
// token
|
||||
GB_TOKEN_STORE = 'GB_TOKEN_STORE',
|
||||
|
||||
// 语言
|
||||
YH_LANG_STORE = 'YH_LANG',
|
||||
//皮肤
|
||||
YH_THEME_STORE = 'YH_THEME',
|
||||
}
|