Compare commits

..

No commits in common. "160773fc380b68b2d1af38c3995f6065fca76b23" and "3552e29a98a68403a61887708f0ac31b26dacc7b" have entirely different histories.

12 changed files with 71 additions and 449 deletions

View File

@ -12,8 +12,6 @@
"@element-plus/icons-vue": "^2.3.2",
"echarts": "^6.0.0",
"element-plus": "^2.13.1",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"vue": "^3.5.24",
"vue-router": "^4.6.4"
},

79
pnpm-lock.yaml generated
View File

@ -17,12 +17,6 @@ importers:
element-plus:
specifier: ^2.13.1
version: 2.13.1(vue@3.5.26(typescript@5.9.3))
pinia:
specifier: ^3.0.4
version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
pinia-plugin-persistedstate:
specifier: ^4.7.1
version: 4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))
vue:
specifier: ^3.5.24
version: 3.5.26(typescript@5.9.3)
@ -593,23 +587,14 @@ packages:
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
'@vue/devtools-api@7.7.9':
resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
'@vue/devtools-core@8.0.5':
resolution: {integrity: sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==}
peerDependencies:
vue: ^3.0.0
'@vue/devtools-kit@7.7.9':
resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
'@vue/devtools-kit@8.0.5':
resolution: {integrity: sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==}
'@vue/devtools-shared@7.7.9':
resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
'@vue/devtools-shared@8.0.5':
resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==}
@ -716,9 +701,6 @@ packages:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
echarts@6.0.0:
resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==}
@ -868,9 +850,6 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
perfect-debounce@2.0.0:
resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}
@ -881,29 +860,6 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
pinia-plugin-persistedstate@4.7.1:
resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==}
peerDependencies:
'@nuxt/kit': '>=3.0.0'
'@pinia/nuxt': '>=0.10.0'
pinia: '>=3.0.0'
peerDependenciesMeta:
'@nuxt/kit':
optional: true
'@pinia/nuxt':
optional: true
pinia:
optional: true
pinia@3.0.4:
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
peerDependencies:
typescript: '>=4.5.0'
vue: ^3.5.11
peerDependenciesMeta:
typescript:
optional: true
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@ -1564,10 +1520,6 @@ snapshots:
'@vue/devtools-api@6.6.4': {}
'@vue/devtools-api@7.7.9':
dependencies:
'@vue/devtools-kit': 7.7.9
'@vue/devtools-core@8.0.5(vite@7.3.1(@types/node@24.10.8))(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@vue/devtools-kit': 8.0.5
@ -1580,16 +1532,6 @@ snapshots:
transitivePeerDependencies:
- vite
'@vue/devtools-kit@7.7.9':
dependencies:
'@vue/devtools-shared': 7.7.9
birpc: 2.9.0
hookable: 5.5.3
mitt: 3.0.1
perfect-debounce: 1.0.0
speakingurl: 14.0.1
superjson: 2.2.6
'@vue/devtools-kit@8.0.5':
dependencies:
'@vue/devtools-shared': 8.0.5
@ -1600,10 +1542,6 @@ snapshots:
speakingurl: 14.0.1
superjson: 2.2.6
'@vue/devtools-shared@7.7.9':
dependencies:
rfdc: 1.4.1
'@vue/devtools-shared@8.0.5':
dependencies:
rfdc: 1.4.1
@ -1713,8 +1651,6 @@ snapshots:
define-lazy-prop@3.0.0: {}
defu@6.1.4: {}
echarts@6.0.0:
dependencies:
tslib: 2.3.0
@ -1859,27 +1795,12 @@ snapshots:
pathe@2.0.3: {}
perfect-debounce@1.0.0: {}
perfect-debounce@2.0.0: {}
picocolors@1.1.1: {}
picomatch@4.0.3: {}
pinia-plugin-persistedstate@4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))):
dependencies:
defu: 6.1.4
optionalDependencies:
pinia: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)):
dependencies:
'@vue/devtools-api': 7.7.9
vue: 3.5.26(typescript@5.9.3)
optionalDependencies:
typescript: 5.9.3
postcss@8.5.6:
dependencies:
nanoid: 3.3.11

View File

@ -5,20 +5,16 @@ const props = defineProps<{
</script>
<template>
<div class="table">
<el-card>
<template #header>
<h3>
<slot name="header"></slot>
</h3>
</template>
<div class="main">
<slot></slot>
</div>
<div class="page">
<el-pagination background :total="props.total" />
</div>
</el-card>
<div class="table box_shadow">
<h3>
<slot name="header"></slot>
</h3>
<div class="main">
<slot></slot>
</div>
<div class="page">
<el-pagination background :total="props.total" />
</div>
</div>
</template>
<style scoped>
@ -29,14 +25,17 @@ const props = defineProps<{
display: flex;
justify-content: space-between;
font-weight: 100;
padding: 20px;
border-bottom: 1px solid #ddd;
}
.main {
padding: 0 10px;
width: 100%;
}
.page {
margin-top: 20px;
padding: 20px;
}
}
</style>

View File

@ -1,5 +1,6 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "@/assets/styles/index.css";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
@ -8,8 +9,6 @@ import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import "@/assets/font/iconfont.css";
import * as echarts from "echarts";
import Components from "@/components";
import pinia from "@/store";
import router from "./router";
const app = createApp(App);
// 全局挂载echarts
@ -18,8 +17,6 @@ app.config.globalProperties.$echarts = echarts;
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.use(pinia);
app.use(router);
app.use(Components);
app.use(ElementPlus, { locale: zhCn }).mount("#app");

View File

@ -5,7 +5,6 @@ import {
} from "vue-router";
import Layout from "@/Layout/index.vue";
import Login from "@/views/login/index.vue";
import { useUserStore } from "@/store";
const routes = [
{
@ -39,21 +38,12 @@ const routes = [
},
],
},
{ path: "/login", name: "Login", component: Login },
{ path: "/login", component: Login },
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach(async (to, from) => {
const userStore = useUserStore();
const isToken = userStore.userInfo?.token;
console.log(userStore);
if (!isToken && to.name !== "Login") {
// 将用户重定向到登录页面
return { name: "Login" };
}
});
export default router;

View File

@ -1,9 +0,0 @@
import { createPinia } from "pinia";
import persist from "pinia-plugin-persistedstate";
const pinia = createPinia();
// 使用持久化存储插件
pinia.use(persist);
export default pinia;
export * from "./modules/user";

View File

@ -1,25 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
interface UserInfoType {
token: string;
}
export const useUserStore = defineStore(
"user",
() => {
const userInfo = ref<UserInfoType | undefined>();
const setUserInfo = (val: UserInfoType) => {
userInfo.value = val;
};
const delUserInfo = () => {
userInfo.value = undefined;
};
return {
userInfo,
setUserInfo,
delUserInfo,
};
},
{
persist: true,
},
);

View File

@ -112,35 +112,23 @@ onMounted(() => {
</script>
<template>
<div class="data">
<div class="info">
<div v-for="item in titleList">
<el-card>
<div class="count">
<i :class="['iconfont', item.icon]"></i>
<div>
<h2>{{ item.count }}</h2>
<text>{{ item.title }}</text>
</div>
</div>
</el-card>
</div>
</div>
<ul>
<li class="box_shadow" v-for="item in titleList">
<i :class="['iconfont', item.icon]"></i>
<div>
<h2>{{ item.count }}</h2>
<text>{{ item.title }}</text>
</div>
</li>
</ul>
<div class="chart">
<div>
<el-card>
<template #headher>
<h3>手术类型分布</h3>
</template>
<div class="dom" ref="typeDom"></div>
</el-card>
<div class="box_shadow">
<h3>设备品牌使用统计</h3>
<div ref="brandDom"></div>
</div>
<div>
<el-card>
<template #headher>
<h3>医生手术统计</h3>
</template>
<div class="dom" ref="brandDom"></div>
</el-card>
<div class="box_shadow">
<h3>设备型号分布</h3>
<div ref="typeDom"></div>
</div>
</div>
<TableBox :total="100">
@ -215,18 +203,16 @@ onMounted(() => {
border-bottom: 1px solid #ddd;
}
.info {
ul {
display: flex;
justify-content: space-between;
gap: 20px;
>div {
flex: 1;
}
.count {
li {
display: flex;
padding: 10px 0;
flex: 1;
height: 120px;
padding: 20px;
align-self: center;
gap: 15px;
@ -252,8 +238,10 @@ onMounted(() => {
>div {
flex: 1;
height: 400px;
.dom {
>div {
width: 100%;
height: 330px;
}

View File

@ -2,9 +2,6 @@
import { ref } from 'vue';
import { User, Lock } from '@element-plus/icons-vue'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { useUserStore } from "@/store";
import router from '@/router';
const userStore = useUserStore()
interface RuleForm {
username: string
password: string
@ -27,7 +24,6 @@ const rules = ref<FormRules<RuleForm>>({
}]
})
const loginBtn = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
@ -35,12 +31,6 @@ const loginBtn = async (formEl: FormInstance | undefined) => {
message: '登录成功',
type: 'success',
})
userStore.setUserInfo({
token: 'aaaaaa'
})
console.log(userStore.userInfo);
router.push('/')
} else {
ElMessage({
message: '请输入账号密码',

View File

@ -153,35 +153,23 @@ onMounted(() => {
</script>
<template>
<div class="data">
<div class="info">
<div v-for="item in titleList">
<el-card>
<div class="count">
<i :class="['iconfont', item.icon]"></i>
<div>
<h2>{{ item.count }}</h2>
<text>{{ item.title }}</text>
</div>
</div>
</el-card>
</div>
</div>
<ul>
<li class="box_shadow" v-for="item in titleList">
<i :class="['iconfont', item.icon]"></i>
<div>
<h2>{{ item.count }}</h2>
<text>{{ item.title }}</text>
</div>
</li>
</ul>
<div class="chart">
<div>
<el-card>
<template #headher>
<h3>手术类型分布</h3>
</template>
<div class="dom" ref="pieDom"></div>
</el-card>
<div class="box_shadow">
<h3>手术类型分布</h3>
<div ref="pieDom"></div>
</div>
<div>
<el-card>
<template #headher>
<h3>医生手术统计</h3>
</template>
<div class="dom" ref="barDom"></div>
</el-card>
<div class="box_shadow">
<h3>医生手术统计</h3>
<div ref="barDom"></div>
</div>
</div>
<TableBox :total="100">
@ -212,22 +200,20 @@ onMounted(() => {
.data {
h3 {
font-weight: 100;
/* padding: 20px;
border-bottom: 1px solid #ddd; */
padding: 20px;
border-bottom: 1px solid #ddd;
}
.info {
ul {
display: flex;
justify-content: space-between;
gap: 20px;
>div {
flex: 1;
}
.count {
li {
display: flex;
padding: 10px 0;
flex: 1;
height: 120px;
padding: 20px;
align-self: center;
gap: 15px;
@ -253,8 +239,10 @@ onMounted(() => {
>div {
flex: 1;
height: 400px;
.dom {
>div {
width: 100%;
height: 330px;
}

View File

@ -1,226 +1,7 @@
<script setup lang='ts'>
import useCurrentInstance from '@/hooks/useCurrentInstance';
import { onMounted, ref } from 'vue';
const options = ref([
{
label: '最近7天',
value: 7
},
{
label: '最近30天',
value: 30
},
{
label: '最近90天',
value: 90
},
{
label: '全部时间',
value: 0
},
])
const { globalProperties } = useCurrentInstance()
const time = ref(90)
const numDom = ref<HTMLDivElement | null>(null)
const typeDom = ref<HTMLDivElement | null>(null)
const wayDom = ref<HTMLDivElement | null>(null)
const surgeryDom = ref<HTMLDivElement | null>(null)
const initialDom = ref<HTMLDivElement | null>(null)
const nowDom = ref<HTMLDivElement | null>(null)
const rederNumDom = () => {
const chart = globalProperties.$echarts.init(numDom.value)
const option = {
grid: {
left: "5%",
right: "5%",
top: 0
},
xAxis: {
type: 'category',
data: ['2026/07/08', '2026/07/08', '2026/07/08', '2026/07/08'],
axisLabel: {
rotate: 50
}
},
yAxis: {
type: 'value'
},
series: [
{
data: [820, 932, 901, 934],
type: 'line',
smooth: true
}
]
};
chart.setOption(option)
}
const redertypeDom = () => {
const chart = globalProperties.$echarts.init(typeDom.value)
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b} : {c} 台手术',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: "rgba(200, 200, 200, .1)"
}
}
},
grid: {
left: '5%',
bottom: '5%',
containLabel: true
},
color: {
type: 'linear',
y2: 1,
colorStops: [{
offset: '0', color: '#3063b0' // 0%
}, {
offset: '1', color: '#519ef8' // 100%
}],
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70],
type: 'bar'
}
]
};
chart.setOption(option)
}
onMounted(() => {
rederNumDom()
redertypeDom()
})
</script>
<script setup lang='ts'></script>
<template>
<div class="statistics">
<div class="top">
<el-card>
<h2>统计分析</h2>
<el-select v-model="time" placeholder="Select" style="width: 240px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<p>当前统计时间范围{{ time === 0 ? '全部时间' : `最近${time}` }} | 统计数据{{ time }}台手术</p>
</el-card>
</div>
<div class="number">
<el-card>
<template #header>
<h3>手术数量趋势</h3>
</template>
<div class="dom" ref="numDom"></div>
</el-card>
</div>
<div class="type">
<el-card>
<template #header>
<h3>脑积水类型分布</h3>
</template>
<div class="dom" ref="typeDom"></div>
</el-card>
<el-card>
<template #header>
<h3>分流方式分布</h3>
</template>
<div class="dom" ref="wayDom"></div>
</el-card>
</div>
<div class="number">
<el-card>
<template #header>
<h3>医生手术统计</h3>
</template>
<div class="dom" ref="surgeryDom"></div>
</el-card>
</div>
<div class="type">
<el-card>
<template #header>
<h3>初始压力分布</h3>
</template>
<div class="dom" ref="initialDom"></div>
</el-card>
<el-card>
<template #header>
<h3>当前压力分布</h3>
</template>
<div class="dom" ref="nowDom"></div>
</el-card>
</div>
<div>
<TableBox :total="100">
<template #header>
最近手术记录 <el-button size="small" type="primary">查看全部</el-button>
</template>
<el-table stripe>
<el-table-column prop="date" label="品牌" width="120" />
<el-table-column prop="name" label="型号" width="150" />
<el-table-column prop="name" label="设备名称" width="150" />
<el-table-column prop="name" label="设备类型" />
<el-table-column prop="name" label="使用次数" width="150" />
<el-table-column prop="name" label="最近使用" width="150" />
<el-table-column prop="name" label="平均初始压力" width="150" />
<el-table-column label="name" width="180">
<template #default="scope">
<el-tag>{{ scope.row.name }}</el-tag>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" width="150">
<template #default>
<el-button link type="primary" size="small">
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
</TableBox>
</div>
<h1>统计分析</h1>
</div>
</template>
<style scoped>
.statistics {
>div {
margin-bottom: 20px;
h3 {
font-weight: 400;
}
}
.dom {
width: 100%;
height: 300px;
}
.type {
display: flex;
gap: 20px;
>div {
flex: 1;
}
}
.top {
h2 {
margin-bottom: 5px;
}
p {
margin-top: 5px;
}
}
}
</style>
<style scoped></style>

View File

@ -233,6 +233,10 @@ const openDialog = () => {
}
.surgery {
/* .form {
padding-top: 20px;
} */
.basic_info {
ul {
border: 1px solid #ebeef5;