main
commit
24b28d428c
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
VITE_API_BASE_URL = '/api'
|
||||||
|
|
||||||
|
VITE_NFT_BASE_URL = 'http://store-manage.hmily.club'
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
VITE_API_BASE_URL = '/api'
|
||||||
|
|
||||||
|
VITE_NFT_BASE_URL = 'http://store-manage.hmily.club'
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Vue 3 + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously Volar) and disable Vetur
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + Vue</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "sotre-manage-echarts",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ant-design-vue": "^4.2.1",
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
"echarts": "^5.3.2",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-router": "^4.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@vueuse/core": "^10.9.0",
|
||||||
|
"unocss": "^0.59.4",
|
||||||
|
"vite": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,8 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
|
|
@ -0,0 +1,122 @@
|
||||||
|
<template>
|
||||||
|
<span :style="{ color }">
|
||||||
|
{{ value }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
watchEffect,
|
||||||
|
unref,
|
||||||
|
onMounted,
|
||||||
|
watch,
|
||||||
|
} from 'vue'
|
||||||
|
import { useTransition, TransitionPresets } from '@vueuse/core'
|
||||||
|
|
||||||
|
const isNumber = (val) => typeof val === 'number'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
startVal: { type: Number, default: 0 },
|
||||||
|
endVal: { type: Number, default: 2021 },
|
||||||
|
duration: { type: Number, default: 1500 },
|
||||||
|
autoplay: { type: Boolean, default: true },
|
||||||
|
decimals: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
validator(value) {
|
||||||
|
return value >= 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prefix: { type: String, default: '' },
|
||||||
|
suffix: { type: String, default: '' },
|
||||||
|
separator: { type: String, default: ',' },
|
||||||
|
decimal: { type: String, default: '.' },
|
||||||
|
/**
|
||||||
|
* font color
|
||||||
|
*/
|
||||||
|
color: { type: String },
|
||||||
|
/**
|
||||||
|
* Turn on digital animation
|
||||||
|
*/
|
||||||
|
useEasing: { type: Boolean, default: true },
|
||||||
|
/**
|
||||||
|
* Digital animation
|
||||||
|
*/
|
||||||
|
transition: { type: String, default: 'linear' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'CountTo',
|
||||||
|
props,
|
||||||
|
emits: ['onStarted', 'onFinished'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const source = ref(props.startVal)
|
||||||
|
const disabled = ref(false)
|
||||||
|
let outputValue = useTransition(source)
|
||||||
|
|
||||||
|
const value = computed(() => formatNumber(unref(outputValue)))
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
source.value = props.startVal
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([() => props.startVal, () => props.endVal], () => {
|
||||||
|
if (props.autoplay) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
props.autoplay && start()
|
||||||
|
})
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
run()
|
||||||
|
source.value = props.endVal
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
source.value = props.startVal
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
outputValue = useTransition(source, {
|
||||||
|
disabled,
|
||||||
|
duration: props.duration,
|
||||||
|
onFinished: () => emit('onFinished'),
|
||||||
|
onStarted: () => emit('onStarted'),
|
||||||
|
...(props.useEasing
|
||||||
|
? { transition: TransitionPresets[props.transition] }
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(num) {
|
||||||
|
if (!num && num !== 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const { decimals, decimal, separator, suffix, prefix } = props
|
||||||
|
num = Number(num).toFixed(decimals)
|
||||||
|
num = parseFloat(num)
|
||||||
|
num += ''
|
||||||
|
|
||||||
|
const x = num.split('.')
|
||||||
|
let x1 = x[0]
|
||||||
|
const x2 = x.length > 1 ? decimal + x[1] : ''
|
||||||
|
|
||||||
|
const rgx = /(\d+)(\d{3})/
|
||||||
|
if (separator && !isNumber(separator)) {
|
||||||
|
while (rgx.test(x1)) {
|
||||||
|
x1 = x1.replace(rgx, '$1' + separator + '$2')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefix + x1 + x2 + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return { value, start, reset }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
msg: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<button type="button" @click="count++">count is {{ count }}</button>
|
||||||
|
<p>
|
||||||
|
Edit
|
||||||
|
<code>components/HelloWorld.vue</code> to test HMR
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Check out
|
||||||
|
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||||
|
>create-vue</a
|
||||||
|
>, the official Vue + Vite starter
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Install
|
||||||
|
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
|
||||||
|
in your IDE for a better DX
|
||||||
|
</p>
|
||||||
|
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ScaleScreen from './src/ScaleScreen.vue'
|
||||||
|
|
||||||
|
export default ScaleScreen
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<transition name="slide">
|
||||||
|
<div
|
||||||
|
v-if="isScroll"
|
||||||
|
v-show="showTop"
|
||||||
|
class="fixed topTool border border-[#396684] bg-[#1c2c34cc] top-0 z-99999 flex text-white py-4px px-10px"
|
||||||
|
>
|
||||||
|
<div class="px-10px cursor-pointer" @click="handleScrollLeft">左</div>
|
||||||
|
<div class="px-10px cursor-pointer" @click="handleScrollCenter">中</div>
|
||||||
|
<div class="px-10px cursor-pointer" @click="handleScrollRight">右</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<section
|
||||||
|
ref="screenRef"
|
||||||
|
:style="{ ...styles.box, ...boxStyle }"
|
||||||
|
class="v-screen-box"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:style="{ ...styles.wrapper, ...wrapperStyle }"
|
||||||
|
class="screen-wrapper"
|
||||||
|
ref="screenWrapper"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
toRef,
|
||||||
|
} from 'vue'
|
||||||
|
/**
|
||||||
|
* 防抖函数
|
||||||
|
* @param {Function} fn
|
||||||
|
* @param {number} delay
|
||||||
|
* @returns {() => void}
|
||||||
|
*/
|
||||||
|
function debounce(fn, delay) {
|
||||||
|
let timer
|
||||||
|
return function (...args) {
|
||||||
|
if (timer) clearTimeout(timer)
|
||||||
|
timer = setTimeout(
|
||||||
|
() => {
|
||||||
|
typeof fn === 'function' && fn.apply(null, args)
|
||||||
|
clearTimeout(timer)
|
||||||
|
},
|
||||||
|
delay > 0 ? delay : 100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ScaleScreen',
|
||||||
|
props: {
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 1920,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 1080,
|
||||||
|
},
|
||||||
|
fullScreen: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
autoScale: {
|
||||||
|
type: [Object, Boolean],
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
delay: {
|
||||||
|
type: Number,
|
||||||
|
default: 500,
|
||||||
|
},
|
||||||
|
boxStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
wrapperStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const state = reactive({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
originalWidth: 0,
|
||||||
|
originalHeight: 0,
|
||||||
|
observer: null,
|
||||||
|
showTop: false,
|
||||||
|
isScroll: false,
|
||||||
|
})
|
||||||
|
const screenRef = ref()
|
||||||
|
const styles = reactive({
|
||||||
|
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`,
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const screenWrapper = ref()
|
||||||
|
/**
|
||||||
|
* 初始化大屏容器宽高
|
||||||
|
*/
|
||||||
|
const initSize = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
nextTick(() => {
|
||||||
|
var _a, _b
|
||||||
|
// region 获取大屏真实尺寸
|
||||||
|
if (props.width && props.height) {
|
||||||
|
state.width = props.width
|
||||||
|
state.height = props.height
|
||||||
|
} else {
|
||||||
|
state.width =
|
||||||
|
(_a = screenWrapper.value) === null || _a === void 0
|
||||||
|
? void 0
|
||||||
|
: _a.clientWidth
|
||||||
|
state.height =
|
||||||
|
(_b = screenWrapper.value) === null || _b === void 0
|
||||||
|
? void 0
|
||||||
|
: _b.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 autoScale = (scale) => {
|
||||||
|
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
|
||||||
|
let currentHeight = document.body.clientHeight
|
||||||
|
if (currentWidth < 3000) currentHeight = currentHeight - 10
|
||||||
|
// 获取大屏最终的宽高
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// 按照宽高最小比例进行缩放
|
||||||
|
let scale = Math.min(widthScale, heightScale)
|
||||||
|
if (currentWidth < 3000) {
|
||||||
|
scale = heightScale
|
||||||
|
state.isScroll = true
|
||||||
|
styles.box['overflow-x'] = 'auto'
|
||||||
|
} else {
|
||||||
|
state.isScroll = false
|
||||||
|
styles.box.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
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 onMouseUpdate = (e) => {
|
||||||
|
if (e.pageY <= 30) state.showTop = true
|
||||||
|
else state.showTop = false
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(async () => {
|
||||||
|
await initSize()
|
||||||
|
updateSize()
|
||||||
|
updateScale()
|
||||||
|
window.addEventListener('resize', onResize)
|
||||||
|
window.addEventListener('mousemove', onMouseUpdate)
|
||||||
|
initMutationObserver()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
var _a
|
||||||
|
window.removeEventListener('resize', onResize)
|
||||||
|
window.removeEventListener('mousemove', onMouseUpdate)
|
||||||
|
;(_a = state.observer) === null || _a === void 0
|
||||||
|
? void 0
|
||||||
|
: _a.disconnect()
|
||||||
|
})
|
||||||
|
const handleScrollLeft = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
var _a
|
||||||
|
;(_a = screenRef.value) === null || _a === void 0
|
||||||
|
? void 0
|
||||||
|
: _a.scrollTo({ left: 0, behavior: 'smooth' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleScrollCenter = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
var _a, _b, _c
|
||||||
|
;(_a = screenRef.value) === null || _a === void 0
|
||||||
|
? void 0
|
||||||
|
: _a.scrollTo({
|
||||||
|
left:
|
||||||
|
((_c =
|
||||||
|
(_b = screenRef.value) === null || _b === void 0
|
||||||
|
? void 0
|
||||||
|
: _b.scrollWidth) !== null && _c !== void 0
|
||||||
|
? _c
|
||||||
|
: 0) /
|
||||||
|
2 -
|
||||||
|
document.body.clientWidth / 2,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleScrollRight = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
var _a, _b
|
||||||
|
;(_a = screenRef.value) === null || _a === void 0
|
||||||
|
? void 0
|
||||||
|
: _a.scrollTo({
|
||||||
|
left:
|
||||||
|
(_b = screenRef.value) === null || _b === void 0
|
||||||
|
? void 0
|
||||||
|
: _b.scrollWidth,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const showTop = toRef(state, 'showTop')
|
||||||
|
const isScroll = toRef(state, 'isScroll')
|
||||||
|
return {
|
||||||
|
screenWrapper,
|
||||||
|
styles,
|
||||||
|
isScroll,
|
||||||
|
showTop,
|
||||||
|
screenRef,
|
||||||
|
handleScrollLeft,
|
||||||
|
handleScrollCenter,
|
||||||
|
handleScrollRight,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.topTool {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as echarts from 'echarts/core'
|
||||||
|
|
||||||
|
import {
|
||||||
|
BarChart,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
MapChart,
|
||||||
|
PictorialBarChart,
|
||||||
|
RadarChart,
|
||||||
|
ScatterChart,
|
||||||
|
} from 'echarts/charts'
|
||||||
|
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
PolarComponent,
|
||||||
|
AriaComponent,
|
||||||
|
ParallelComponent,
|
||||||
|
LegendComponent,
|
||||||
|
RadarComponent,
|
||||||
|
ToolboxComponent,
|
||||||
|
DataZoomComponent,
|
||||||
|
VisualMapComponent,
|
||||||
|
TimelineComponent,
|
||||||
|
CalendarComponent,
|
||||||
|
GraphicComponent,
|
||||||
|
} from 'echarts/components'
|
||||||
|
|
||||||
|
import { SVGRenderer } from 'echarts/renderers'
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
LegendComponent,
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
PolarComponent,
|
||||||
|
AriaComponent,
|
||||||
|
ParallelComponent,
|
||||||
|
BarChart,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
MapChart,
|
||||||
|
RadarChart,
|
||||||
|
SVGRenderer,
|
||||||
|
PictorialBarChart,
|
||||||
|
RadarComponent,
|
||||||
|
ToolboxComponent,
|
||||||
|
DataZoomComponent,
|
||||||
|
VisualMapComponent,
|
||||||
|
TimelineComponent,
|
||||||
|
CalendarComponent,
|
||||||
|
GraphicComponent,
|
||||||
|
ScatterChart,
|
||||||
|
])
|
||||||
|
|
||||||
|
export default echarts
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function isFunction(val) {
|
||||||
|
return typeof val === 'function'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { ref, computed, unref } from 'vue';
|
||||||
|
import { useEventListener } from '/@/hooks/event/useEventListener';
|
||||||
|
import { screenMap, sizeEnum, screenEnum } from '/@/enums/breakpointEnum';
|
||||||
|
let globalScreenRef;
|
||||||
|
let globalWidthRef;
|
||||||
|
let globalRealWidthRef;
|
||||||
|
export function useBreakpoint() {
|
||||||
|
return {
|
||||||
|
screenRef: computed(() => unref(globalScreenRef)),
|
||||||
|
widthRef: globalWidthRef,
|
||||||
|
screenEnum,
|
||||||
|
realWidthRef: globalRealWidthRef,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Just call it once
|
||||||
|
export function createBreakpointListen(fn) {
|
||||||
|
const screenRef = ref(sizeEnum.XL);
|
||||||
|
const realWidthRef = ref(window.innerWidth);
|
||||||
|
function getWindowWidth() {
|
||||||
|
const width = document.body.clientWidth;
|
||||||
|
const xs = screenMap.get(sizeEnum.XS);
|
||||||
|
const sm = screenMap.get(sizeEnum.SM);
|
||||||
|
const md = screenMap.get(sizeEnum.MD);
|
||||||
|
const lg = screenMap.get(sizeEnum.LG);
|
||||||
|
const xl = screenMap.get(sizeEnum.XL);
|
||||||
|
if (width < xs) {
|
||||||
|
screenRef.value = sizeEnum.XS;
|
||||||
|
}
|
||||||
|
else if (width < sm) {
|
||||||
|
screenRef.value = sizeEnum.SM;
|
||||||
|
}
|
||||||
|
else if (width < md) {
|
||||||
|
screenRef.value = sizeEnum.MD;
|
||||||
|
}
|
||||||
|
else if (width < lg) {
|
||||||
|
screenRef.value = sizeEnum.LG;
|
||||||
|
}
|
||||||
|
else if (width < xl) {
|
||||||
|
screenRef.value = sizeEnum.XL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
screenRef.value = sizeEnum.XXL;
|
||||||
|
}
|
||||||
|
realWidthRef.value = width;
|
||||||
|
}
|
||||||
|
useEventListener({
|
||||||
|
el: window,
|
||||||
|
name: 'resize',
|
||||||
|
listener: () => {
|
||||||
|
getWindowWidth();
|
||||||
|
resizeFn();
|
||||||
|
},
|
||||||
|
// wait: 100,
|
||||||
|
});
|
||||||
|
getWindowWidth();
|
||||||
|
globalScreenRef = computed(() => unref(screenRef));
|
||||||
|
globalWidthRef = computed(() => screenMap.get(unref(screenRef)));
|
||||||
|
globalRealWidthRef = computed(() => unref(realWidthRef));
|
||||||
|
function resizeFn() {
|
||||||
|
fn === null || fn === void 0 ? void 0 : fn({
|
||||||
|
screen: globalScreenRef,
|
||||||
|
width: globalWidthRef,
|
||||||
|
realWidth: globalRealWidthRef,
|
||||||
|
screenEnum,
|
||||||
|
screenMap,
|
||||||
|
sizeEnum,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resizeFn();
|
||||||
|
return {
|
||||||
|
screenRef: globalScreenRef,
|
||||||
|
screenEnum,
|
||||||
|
widthRef: globalWidthRef,
|
||||||
|
realWidthRef: globalRealWidthRef,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { useTimeoutFn } from './useTimeout.js';
|
||||||
|
import { tryOnUnmounted } from '@vueuse/core';
|
||||||
|
import { unref, nextTick, watch, computed, ref } from 'vue';
|
||||||
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
import { useEventListener } from './useEventListener.js';
|
||||||
|
import echarts from './echarts.js';
|
||||||
|
export function useECharts(elRef, theme = 'default') {
|
||||||
|
const getDarkMode = computed(() => {
|
||||||
|
return 'light'
|
||||||
|
});
|
||||||
|
let chartInstance = null;
|
||||||
|
let resizeFn = resize;
|
||||||
|
const cacheOptions = ref({});
|
||||||
|
let removeResizeFn = () => { };
|
||||||
|
resizeFn = useDebounceFn(resize, 200);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
if (getDarkMode.value !== 'dark') {
|
||||||
|
return cacheOptions.value;
|
||||||
|
}
|
||||||
|
return Object.assign({ backgroundColor: 'transparent' }, cacheOptions.value);
|
||||||
|
});
|
||||||
|
function initCharts(t = theme) {
|
||||||
|
const el = unref(elRef);
|
||||||
|
if (!el || !unref(el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chartInstance = echarts.init(el, t);
|
||||||
|
const { removeEvent } = useEventListener({
|
||||||
|
el: window,
|
||||||
|
name: 'resize',
|
||||||
|
listener: resizeFn,
|
||||||
|
});
|
||||||
|
removeResizeFn = removeEvent;
|
||||||
|
// useTimeoutFn(() => {
|
||||||
|
// resizeFn();
|
||||||
|
// }, 30);
|
||||||
|
}
|
||||||
|
function setOptions(options, clear = true) {
|
||||||
|
var _a;
|
||||||
|
cacheOptions.value = options;
|
||||||
|
if (((_a = unref(elRef)) === null || _a === void 0 ? void 0 : _a.offsetHeight) === 0) {
|
||||||
|
useTimeoutFn(() => {
|
||||||
|
setOptions(unref(getOptions));
|
||||||
|
}, 30);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
useTimeoutFn(() => {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initCharts(getDarkMode.value);
|
||||||
|
if (!chartInstance)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clear && (chartInstance === null || chartInstance === void 0 ? void 0 : chartInstance.clear());
|
||||||
|
chartInstance === null || chartInstance === void 0 ? void 0 : chartInstance.setOption(unref(getOptions));
|
||||||
|
}, 30);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function resize() {
|
||||||
|
chartInstance === null || chartInstance === void 0 ? void 0 : chartInstance.resize({
|
||||||
|
animation: {
|
||||||
|
duration: 300,
|
||||||
|
easing: 'quadraticIn',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
watch(() => getDarkMode.value, (theme) => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
initCharts(theme);
|
||||||
|
setOptions(cacheOptions.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tryOnUnmounted(() => {
|
||||||
|
if (!chartInstance)
|
||||||
|
return;
|
||||||
|
removeResizeFn();
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
});
|
||||||
|
function getInstance() {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initCharts(getDarkMode.value);
|
||||||
|
}
|
||||||
|
return chartInstance;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
setOptions,
|
||||||
|
resize,
|
||||||
|
echarts,
|
||||||
|
getInstance,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { ref, watch, unref } from 'vue';
|
||||||
|
import { useThrottleFn, useDebounceFn } from '@vueuse/core';
|
||||||
|
export function useEventListener({ el = window, name, listener, options, autoRemove = true, isDebounce = true, wait = 80, }) {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
let remove = () => { };
|
||||||
|
const isAddRef = ref(false);
|
||||||
|
if (el) {
|
||||||
|
const element = ref(el);
|
||||||
|
const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait);
|
||||||
|
const realHandler = wait ? handler : listener;
|
||||||
|
const removeEventListener = (e) => {
|
||||||
|
isAddRef.value = true;
|
||||||
|
e.removeEventListener(name, realHandler, options);
|
||||||
|
};
|
||||||
|
const addEventListener = (e) => e.addEventListener(name, realHandler, options);
|
||||||
|
const removeWatch = watch(element, (v, _ov, cleanUp) => {
|
||||||
|
if (v) {
|
||||||
|
!unref(isAddRef) && addEventListener(v);
|
||||||
|
cleanUp(() => {
|
||||||
|
autoRemove && removeEventListener(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
remove = () => {
|
||||||
|
removeEventListener(element.value);
|
||||||
|
removeWatch();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { removeEvent: remove };
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { tryOnUnmounted } from '@vueuse/core'
|
||||||
|
import { isFunction } from './is.js'
|
||||||
|
|
||||||
|
export function useTimeoutFn(handle, wait, native = false) {
|
||||||
|
if (!isFunction(handle)) {
|
||||||
|
throw new Error('handle is not Function!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { readyRef, stop, start } = useTimeoutRef(wait)
|
||||||
|
if (native) {
|
||||||
|
handle()
|
||||||
|
} else {
|
||||||
|
watch(
|
||||||
|
readyRef,
|
||||||
|
(maturity) => {
|
||||||
|
maturity && handle()
|
||||||
|
},
|
||||||
|
{ immediate: false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { readyRef, stop, start }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTimeoutRef(wait) {
|
||||||
|
const readyRef = ref(false)
|
||||||
|
|
||||||
|
let timer
|
||||||
|
function stop() {
|
||||||
|
readyRef.value = false
|
||||||
|
timer && window.clearTimeout(timer)
|
||||||
|
}
|
||||||
|
function start() {
|
||||||
|
stop()
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
readyRef.value = true
|
||||||
|
}, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
|
|
||||||
|
tryOnUnmounted(stop)
|
||||||
|
|
||||||
|
return { readyRef, stop, start }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import Antd from 'ant-design-vue';
|
||||||
|
import './style.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
import 'ant-design-vue/dist/reset.css';
|
||||||
|
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(Antd)
|
||||||
|
app.mount('#app')
|
||||||
|
}
|
||||||
|
bootstrap()
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
//路由初始指向
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: () => import('../views/home/index.vue'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.04);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||||
|
timeout: 200000,
|
||||||
|
withCredentials: false
|
||||||
|
})
|
||||||
|
|
||||||
|
service.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
service.interceptors.response.use(
|
||||||
|
|
||||||
|
response => {
|
||||||
|
|
||||||
|
const res = response
|
||||||
|
const config = response.config
|
||||||
|
if (res.status >=400) {
|
||||||
|
|
||||||
|
return Promise.reject(res.msg || 'Error')
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const { isTransformResponse } = config
|
||||||
|
if (isTransformResponse) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="flex justify-between h-10 items-center px-4">
|
||||||
|
<div class="text-[#999] text-sm">年度目标</div>
|
||||||
|
<div class="w-30">
|
||||||
|
<a-select
|
||||||
|
ref="select"
|
||||||
|
v-model:value="day"
|
||||||
|
class="w-full"
|
||||||
|
@change="onChange"
|
||||||
|
>
|
||||||
|
<a-select-option :value="item" v-for="item in years" :key="item">{{
|
||||||
|
item
|
||||||
|
}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1" ref="yRef"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useECharts } from '@/components/echarts/useECharts.js'
|
||||||
|
import echarts from '@/components/echarts/echarts.js'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
const day = ref(null)
|
||||||
|
const yRef = ref(null)
|
||||||
|
const { setOptions } = useECharts(yRef)
|
||||||
|
const years = ref([])
|
||||||
|
const datas = ref({})
|
||||||
|
|
||||||
|
const createFiveYears = () => {
|
||||||
|
const now = new Date()
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
years.value.push(now.getFullYear() - i)
|
||||||
|
}
|
||||||
|
day.value = years.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
createFiveYears()
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const onChange = () => {
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get('/admin-api/api/cockpit/yearly-goals', {
|
||||||
|
params: {
|
||||||
|
year: day.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
datas.value = resData
|
||||||
|
chatInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatInit = () => {
|
||||||
|
const { expected_performance, actual_performance } = datas.value
|
||||||
|
let rate = (actual_performance / expected_performance).toFixed(2)
|
||||||
|
console.log(rate);
|
||||||
|
if (rate == 'NaN') {
|
||||||
|
rate = 0
|
||||||
|
}
|
||||||
|
const rateValue = (rate * 100).toFixed(0)
|
||||||
|
setOptions({
|
||||||
|
title: {
|
||||||
|
text: `${rateValue}%`,
|
||||||
|
x: 'center',
|
||||||
|
y: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
color: '#000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: ['#AAAFC8'],
|
||||||
|
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Line1',
|
||||||
|
type: 'pie',
|
||||||
|
clockWise: true,
|
||||||
|
radius: ['80%', '70%'],
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hoverAnimation: false,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: rateValue,
|
||||||
|
name: '01',
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
color: '#0E7CE2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '02',
|
||||||
|
value: 100 - rateValue,
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
color: '#AAAFC8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="flex justify-between p-4">
|
||||||
|
<div class="text-[#999] text-sm">数据概览</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 grid grid-cols-2 py-5">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-2xl font-medium">门店总数</div>
|
||||||
|
<div class="my-5">
|
||||||
|
<CountTo class="text-2xl" :endVal="deta?.stores_count || 0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-2xl font-medium">员工总数</div>
|
||||||
|
<div class="my-5">
|
||||||
|
<CountTo class="text-2xl" :endVal="deta?.employees_count || 0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import CountTo from '@/components/CountTo/index.vue'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
const deta = ref({})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get('/admin-api/api/cockpit/basic')
|
||||||
|
deta.value = resData
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="flex justify-between p-4">
|
||||||
|
<div class="text-[#999] text-sm">彩种销售走势</div>
|
||||||
|
<div class="w-50">
|
||||||
|
<a-select ref="select" v-model:value="day" class="w-full" @change="onChange">
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in dayList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>{{ item.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1" ref="trendRef"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useECharts } from '@/components/echarts/useECharts.js'
|
||||||
|
import echarts from '@/components/echarts/echarts.js'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
const datas = ref([])
|
||||||
|
const day = ref('7days')
|
||||||
|
const dayList = [
|
||||||
|
{
|
||||||
|
value: '7days',
|
||||||
|
label: '近7天',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '30days',
|
||||||
|
label: '近30天',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '180days',
|
||||||
|
label: '进半年',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '365days',
|
||||||
|
label: '近一年',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const trendRef = ref(null)
|
||||||
|
const { setOptions } = useECharts(trendRef)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get('/admin-api/api/cockpit/lottery-sales-trend', {
|
||||||
|
params: {
|
||||||
|
last: day.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
datas.value = resData.data
|
||||||
|
chatInit()
|
||||||
|
}
|
||||||
|
const chatInit = () => {
|
||||||
|
const groupedData = {}
|
||||||
|
|
||||||
|
datas.value.forEach((item) => {
|
||||||
|
item.data.forEach((entry) => {
|
||||||
|
const { name, sales } = entry
|
||||||
|
if (!groupedData[name]) {
|
||||||
|
groupedData[name] = []
|
||||||
|
}
|
||||||
|
groupedData[name].push({ date: item.date, sales })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const xAxisData = []
|
||||||
|
const seriesData = []
|
||||||
|
Object.keys(groupedData).forEach((name) => {
|
||||||
|
xAxisData.push(name)
|
||||||
|
seriesData.push({
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.1,
|
||||||
|
// normal: {
|
||||||
|
// // color: new echarts.graphic.LinearGradient(
|
||||||
|
// // 0,
|
||||||
|
// // 0,
|
||||||
|
// // 0,
|
||||||
|
// // 1,
|
||||||
|
// // [
|
||||||
|
// // {
|
||||||
|
// // offset: 0,
|
||||||
|
// // color: 'rgba(25,163,223,.3)',
|
||||||
|
// // },
|
||||||
|
// // {
|
||||||
|
// // offset: 1,
|
||||||
|
// // color: 'rgba(25,163,223, 0)',
|
||||||
|
// // },
|
||||||
|
// // ],
|
||||||
|
// // false
|
||||||
|
// // ),
|
||||||
|
// opacity: 0.1,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
data: groupedData[name].map((item) => item.sales),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setOptions({
|
||||||
|
title: {
|
||||||
|
textStyle: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#F1F1F3',
|
||||||
|
},
|
||||||
|
left: '6%',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
icon: 'rect',
|
||||||
|
itemWidth: 14,
|
||||||
|
itemHeight: 5,
|
||||||
|
itemGap: 13,
|
||||||
|
data: xAxisData,
|
||||||
|
right: '4%',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#292f39',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#999',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: datas.value.map((e) => e.date ?? e.month),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
margin: 10,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#eee',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: seriesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full" ref="mapRef">12</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useECharts } from '@/components/echarts/useECharts.js'
|
||||||
|
import chinaMap from './china.json'
|
||||||
|
import { registerMap } from 'echarts'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
const mapRef = ref(null)
|
||||||
|
const { setOptions } = useECharts(mapRef)
|
||||||
|
const data = ref([])
|
||||||
|
|
||||||
|
registerMap('china', { geoJSON: chinaMap })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get(
|
||||||
|
'/admin-api/api/cockpit/store-number-distribution'
|
||||||
|
)
|
||||||
|
data.value = resData
|
||||||
|
chatInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertData = (data) => {
|
||||||
|
var geoCoordMap = []
|
||||||
|
chinaMap.features.forEach((item) => {
|
||||||
|
geoCoordMap[item.properties.name] = item.properties.center
|
||||||
|
})
|
||||||
|
var res = []
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var geoCoord = geoCoordMap[data[i].name]
|
||||||
|
if (geoCoord && data[i].stores_count > 0) {
|
||||||
|
res.push({
|
||||||
|
name: data[i].name,
|
||||||
|
value: geoCoord.concat(data[i].stores_count),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatInit = () => {
|
||||||
|
var max = 480,
|
||||||
|
min = 14 // todo
|
||||||
|
var maxSize4Pin = 100,
|
||||||
|
minSize4Pin = 30
|
||||||
|
|
||||||
|
setOptions({
|
||||||
|
baseOption: {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: function (params) {
|
||||||
|
if (typeof params.value[2] == 'undefined') {
|
||||||
|
return params.name + ' : ' + params.value
|
||||||
|
} else {
|
||||||
|
return params.name + ' : ' + params.value[2]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
geo: {
|
||||||
|
map: 'china',
|
||||||
|
zoom: 1.5,
|
||||||
|
roam: false,
|
||||||
|
left: '20%',
|
||||||
|
top: '23%',
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: 'rgb(249, 249, 249)', //省份标签字体颜色
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
areaColor: '#24CFF4',
|
||||||
|
borderColor: '#53D9FF',
|
||||||
|
borderWidth: 1.3,
|
||||||
|
shadowBlur: 15,
|
||||||
|
shadowColor: 'rgb(58,115,192)',
|
||||||
|
shadowOffsetX: 7,
|
||||||
|
shadowOffsetY: 6,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
areaColor: '#8dd7fc',
|
||||||
|
borderWidth: 1.6,
|
||||||
|
shadowBlur: 25,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#f75a00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'map',
|
||||||
|
map: 'china',
|
||||||
|
geoIndex: 0,
|
||||||
|
aspectScale: 0.75, //长宽比
|
||||||
|
showLegendSymbol: false, // 存在legend时显示
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
show: false,
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
roam: true,
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
areaColor: '#031525',
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
areaColor: '#2B91B7',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: false,
|
||||||
|
data: data.value.map((e) => {
|
||||||
|
return {
|
||||||
|
name: e.name,
|
||||||
|
value: e.stores_count,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '点',
|
||||||
|
type: 'scatter',
|
||||||
|
coordinateSystem: 'geo',
|
||||||
|
symbol: 'pin',
|
||||||
|
symbolSize: function (val) {
|
||||||
|
var a = (maxSize4Pin - minSize4Pin) / (max - min)
|
||||||
|
var b = minSize4Pin - a * min
|
||||||
|
b = maxSize4Pin - a * max
|
||||||
|
return a * val[2] + b
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
formatter(value) {
|
||||||
|
return value.data.value[2]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#F62157',
|
||||||
|
},
|
||||||
|
zlevel: 6,
|
||||||
|
data: convertData(data.value),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="flex justify-between items-center p-4">
|
||||||
|
<div class="text-[#999] text-sm">数据排行</div>
|
||||||
|
<div class="w-30">
|
||||||
|
<a-select
|
||||||
|
ref="select"
|
||||||
|
v-model:value="day"
|
||||||
|
class="w-full"
|
||||||
|
@change="onChange"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in dayList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>{{ item.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1" ref="rankRef"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useECharts } from '@/components/echarts/useECharts.js'
|
||||||
|
import echarts from '@/components/echarts/echarts.js'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
const day = ref('365days')
|
||||||
|
const dayList = [
|
||||||
|
{
|
||||||
|
value: '7days',
|
||||||
|
label: '近7天',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '30days',
|
||||||
|
label: '近30天',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '180days',
|
||||||
|
label: '进半年',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '365days',
|
||||||
|
label: '近一年',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const rankRef = ref(null)
|
||||||
|
const { setOptions } = useECharts(rankRef)
|
||||||
|
|
||||||
|
let dataList = ref([])
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const onChange = () => {
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get('/admin-api/api/cockpit/store-sales-ranking', {
|
||||||
|
params: {
|
||||||
|
last: day.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const arr = resData.map((e, i) => {
|
||||||
|
return [i + 1, e.store.title, e.sales]
|
||||||
|
})
|
||||||
|
dataList.value = arr.filter((e) => e[2] > 0)
|
||||||
|
chatInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatInit = () => {
|
||||||
|
__SetOption(dataList.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
let barWidth = 15
|
||||||
|
let nameFontColor = '#000'
|
||||||
|
let nameFontSize = 12
|
||||||
|
let namePosition = [0, -15]
|
||||||
|
let valueFontColor = '#FFFFFF'
|
||||||
|
let valueFontSize = 12
|
||||||
|
let indexNum = 0
|
||||||
|
|
||||||
|
function __getColorValue(name, val, index) {
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
color: '#000',
|
||||||
|
value: val,
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
show: true,
|
||||||
|
color: '#0E7CE2',
|
||||||
|
barBorderRadius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function __SetOption(data) {
|
||||||
|
let lists = []
|
||||||
|
let values = []
|
||||||
|
data.forEach((item, index) => {
|
||||||
|
lists.push(item[1])
|
||||||
|
values.push(__getColorValue(item[1], item[2], index))
|
||||||
|
})
|
||||||
|
|
||||||
|
let option = {
|
||||||
|
grid: {
|
||||||
|
top: 0,
|
||||||
|
left: '20px',
|
||||||
|
right: '3%',
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
inverse: true,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: { show: false },
|
||||||
|
axisLabel: { show: false, inside: false },
|
||||||
|
data: lists,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: { show: false },
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLabel: { show: false },
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
// {
|
||||||
|
// show: true,
|
||||||
|
// type: 'bar',
|
||||||
|
// barGap: '-100%',
|
||||||
|
// barWidth: barWidth,
|
||||||
|
// itemStyle: {
|
||||||
|
// normal: {
|
||||||
|
// color: 'rgba(102, 102, 102,0.5)',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// z: 1,
|
||||||
|
// data: values.map(()=>1),
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: '排行',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: barWidth,
|
||||||
|
data: values,
|
||||||
|
animationDuration: 1500,
|
||||||
|
zlevel: 2,
|
||||||
|
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
show: true,
|
||||||
|
color: nameFontColor,
|
||||||
|
show: true,
|
||||||
|
position: 'inside',
|
||||||
|
position: namePosition,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: nameFontSize,
|
||||||
|
},
|
||||||
|
formatter: function (data) {
|
||||||
|
return `${data.data.name}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
setOptions(option)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="flex justify-between items-center p-4">
|
||||||
|
<div class="text-[#999] text-sm">销售走势</div>
|
||||||
|
<div class="w-30">
|
||||||
|
<a-select
|
||||||
|
ref="select"
|
||||||
|
v-model:value="day"
|
||||||
|
class="w-full"
|
||||||
|
@change="onChange"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in dayList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>{{ item.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1" ref="trendRef1"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useECharts } from '@/components/echarts/useECharts.js'
|
||||||
|
import echarts from '@/components/echarts/echarts.js'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
const datas = ref([])
|
||||||
|
const day = ref('7days')
|
||||||
|
const dayList = [
|
||||||
|
{
|
||||||
|
value: '7days',
|
||||||
|
label: '近7天',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '30days',
|
||||||
|
label: '近30天',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '180days',
|
||||||
|
label: '进半年',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '365days',
|
||||||
|
label: '近一年',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const trendRef1 = ref(null)
|
||||||
|
const { setOptions } = useECharts(trendRef1)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get('/admin-api/api/cockpit/sales-trend', {
|
||||||
|
params: {
|
||||||
|
last: day.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
datas.value = resData
|
||||||
|
|
||||||
|
chatInit()
|
||||||
|
}
|
||||||
|
const chatInit = () => {
|
||||||
|
const xAxisData = []
|
||||||
|
const sales = []
|
||||||
|
datas.value.forEach((item) => {
|
||||||
|
xAxisData.push(item.date ?? item.month)
|
||||||
|
sales.push(item.sales)
|
||||||
|
})
|
||||||
|
|
||||||
|
const seriesData = []
|
||||||
|
seriesData.push({
|
||||||
|
name: '销售',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.1,
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
data: sales,
|
||||||
|
})
|
||||||
|
|
||||||
|
setOptions({
|
||||||
|
title: {
|
||||||
|
textStyle: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#F1F1F3',
|
||||||
|
},
|
||||||
|
left: '6%',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
icon: 'rect',
|
||||||
|
itemWidth: 14,
|
||||||
|
itemHeight: 5,
|
||||||
|
itemGap: 13,
|
||||||
|
// data: xAxisData,
|
||||||
|
right: '4%',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#292f39',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#999',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: xAxisData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
margin: 10,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#eee',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: seriesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="flex justify-between p-4">
|
||||||
|
<div class="text-[#999] text-sm">门店分类</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1" ref="sortRef"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useECharts } from '@/components/echarts/useECharts.js'
|
||||||
|
import echarts from '@/components/echarts/echarts.js'
|
||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
const sortRef = ref(null)
|
||||||
|
const datas = ref([])
|
||||||
|
const { setOptions } = useECharts(sortRef)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const resData = await http.get('/admin-api/api/cockpit/store-category')
|
||||||
|
datas.value = resData
|
||||||
|
chatInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatInit = () => {
|
||||||
|
setOptions({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b} : {c} ({d}%)',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '门店',
|
||||||
|
type: 'pie',
|
||||||
|
radius: '90%',
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
clockwise: false,
|
||||||
|
data: datas.value.map(e=>{
|
||||||
|
return {
|
||||||
|
name: e.name,
|
||||||
|
value: e.stores_count
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
|
||||||
|
borderColor: '#ffffff',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
borderWidth: 0,
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-5 flex flex-col">
|
||||||
|
<div class="">驾驶舱</div>
|
||||||
|
<div class="flex flex-1 space-x-4">
|
||||||
|
<div class="w-6/10 space-y-4" v-if="true">
|
||||||
|
<div class="h-150 box-shadow">
|
||||||
|
<Map />
|
||||||
|
</div>
|
||||||
|
<div class="h-75 box-shadow">
|
||||||
|
<LotteryTrends />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 space-y-4">
|
||||||
|
<div class="box-shadow">
|
||||||
|
<Count />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-x-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="box-shadow h-56">
|
||||||
|
<Sort />
|
||||||
|
</div>
|
||||||
|
<div class="box-shadow h-56">
|
||||||
|
<SalesTrend />
|
||||||
|
</div>
|
||||||
|
<div class="box-shadow h-56">
|
||||||
|
<AnnualTarget />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-shadow">
|
||||||
|
<Rank />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import Map from './components/map.vue'
|
||||||
|
import LotteryTrends from './components/lottery-trends.vue'
|
||||||
|
import Count from './components/count.vue'
|
||||||
|
import Sort from "./components/sort.vue"
|
||||||
|
import AnnualTarget from './components/annual-target.vue'
|
||||||
|
import Rank from './components/rank.vue'
|
||||||
|
import SalesTrend from './components/sales-trend.vue'
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// uno.config.ts
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
presetIcons,
|
||||||
|
presetTypography,
|
||||||
|
presetUno,
|
||||||
|
presetWebFonts,
|
||||||
|
transformerDirectives,
|
||||||
|
transformerVariantGroup
|
||||||
|
} from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
shortcuts: [
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
presets: [
|
||||||
|
presetUno(),
|
||||||
|
presetAttributify(),
|
||||||
|
presetIcons(),
|
||||||
|
presetTypography(),
|
||||||
|
presetWebFonts({
|
||||||
|
fonts: {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
transformers: [transformerDirectives(), transformerVariantGroup()]
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue(), UnoCSS()],
|
||||||
|
server:{
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://store-manage.hmily.club',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue