<template>
|
<div v-loading="loading" class="cockpit">
|
<!-- 顶部标题栏 -->
|
<header class="cockpit-header">
|
<div class="header-left">
|
<span class="header-logo"></span>
|
<h1 class="header-title">青岛大学附属医院 · OPO 器官捐献管理驾驶舱</h1>
|
</div>
|
<div class="header-right">
|
<span class="header-clock">{{ now }}</span>
|
<el-button type="text" class="header-logout" @click="handleLogout"
|
>退出</el-button
|
>
|
</div>
|
</header>
|
|
<!-- KPI 指标卡 -->
|
<section class="kpi-row">
|
<div v-for="kpi in kpiCards" :key="kpi.label" class="kpi-card">
|
<div class="kpi-icon" :style="{ background: kpi.gradient }">
|
<i :class="kpi.icon"></i>
|
</div>
|
<div class="kpi-body">
|
<div class="kpi-label">{{ kpi.label }}</div>
|
<div class="kpi-value">
|
<count-to
|
:start-val="0"
|
:end-val="kpi.value"
|
:duration="1400"
|
:decimals="kpi.decimals || 0"
|
separator=","
|
/>
|
<span class="kpi-unit">{{ kpi.unit }}</span>
|
</div>
|
</div>
|
</div>
|
</section>
|
|
<!-- 仪表盘指标 -->
|
<section class="gauge-row">
|
<div v-for="g in gaugeCharts" :key="g.name" class="panel">
|
<div class="panel-title">{{ g.name }}</div>
|
<G2PlotChart type="gauge" :options="g.options" :height="150" />
|
</div>
|
</section>
|
|
<!-- 主区:地图居中,其他图环绕 -->
|
<el-row :gutter="16">
|
<el-col :span="6">
|
<div class="col-stack">
|
<div class="panel">
|
<div class="panel-title">案例月度趋势</div>
|
<G2PlotChart type="area" :options="trendOptions" :height="205" />
|
</div>
|
<div class="panel">
|
<div class="panel-title">器官获取 / 移植趋势</div>
|
<G2PlotChart type="line" :options="organMoonOptions" :height="205" />
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="panel panel--map">
|
<div class="panel-title">地区分布</div>
|
<MapChart
|
:cdata="raw.mapData"
|
:height="'480px'"
|
:zoom="1.4"
|
left="center"
|
top="middle"
|
label-mode="name"
|
dark
|
:roam="true"
|
@click="mapClick"
|
/>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="col-stack">
|
<div class="panel">
|
<div class="panel-title">地区捐献对比</div>
|
<G2PlotChart type="column" :options="areaOptions" :height="205" />
|
</div>
|
<div class="panel panel--carousel">
|
<div class="panel-title">{{ currentCarousel.title }}</div>
|
<transition name="fade" mode="out-in">
|
<div :key="carouselIndex" class="carousel-body">
|
<G2PlotChart
|
:type="currentCarousel.type"
|
:options="currentCarousel.options"
|
:height="205"
|
/>
|
</div>
|
</transition>
|
<div class="carousel-dots">
|
<span
|
v-for="(item, i) in carouselItems"
|
:key="i"
|
class="dot"
|
:class="{ active: i === carouselIndex }"
|
@click="carouselIndex = i"
|
></span>
|
</div>
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<!-- 登录弹窗 -->
|
<el-dialog
|
title="登录"
|
:visible.sync="dialogVisible"
|
width="380px"
|
:close-on-click-modal="false"
|
:show-close="false"
|
append-to-body
|
custom-class="login-dialog"
|
>
|
<el-form :model="form" @submit.native.prevent>
|
<el-form-item label="账号" label-width="60px">
|
<el-input
|
v-model="defaultAccount"
|
autocomplete="off"
|
placeholder="请输入账号"
|
@keyup.enter.native="loginFn"
|
></el-input>
|
</el-form-item>
|
<el-form-item label="密码" label-width="60px">
|
<el-input
|
v-model="defaultPassword"
|
show-password
|
autocomplete="off"
|
placeholder="请输入密码"
|
@keyup.enter.native="loginFn"
|
></el-input>
|
</el-form-item>
|
</el-form>
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button type="primary" :loading="loginLoading" @click="loginFn"
|
>登 录</el-button
|
>
|
</span>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
import CountTo from 'vue-count-to';
|
import G2PlotChart from '@/components/charts/G2PlotChart.vue';
|
import MapChart from './components/MapChart';
|
import { login } from '@/api/login';
|
import { encrypts } from '@/utils/crypto';
|
import { setToken, removeToken } from '@/utils/auth';
|
import { xAxis, yAxis, legend, tooltip } from '@/utils/g2Dark';
|
import { toMonth, toNum } from '@/utils/totals';
|
import { mockCockpitData } from './cockpitMock';
|
|
const AGE_LABELS = {
|
'1': '≤17岁',
|
'2': '18-49岁',
|
'3': '50-69岁',
|
'4': '≥70岁',
|
'0-17': '≤17岁',
|
'18-49': '18-49岁',
|
'50-69': '50-69岁',
|
'70-100': '≥70岁'
|
};
|
|
export default {
|
name: 'home',
|
components: { CountTo, G2PlotChart, MapChart },
|
data() {
|
return {
|
now: '',
|
clockTimer: null,
|
carouselTimer: null,
|
loading: false,
|
loginLoading: false,
|
dialogVisible: true,
|
defaultAccount: '',
|
defaultPassword: '',
|
form: {},
|
carouselIndex: 0,
|
raw: {
|
mapData: [],
|
gauges: [],
|
caseTrend: [],
|
organMoon: [],
|
caseBloodType: [],
|
caseSex: [],
|
organType: [],
|
organDiscard: [],
|
caseTrendArea: [],
|
totals: [],
|
caseAvgAge: []
|
}
|
};
|
},
|
computed: {
|
kpiCards() {
|
const t = this.raw.totals[0] || {};
|
const potential = toNum(t.totalPotential);
|
const completed = toNum(t.totalCompletion);
|
const age = toNum((this.raw.caseAvgAge[0] || {}).avgAge);
|
const getTotal = this.raw.organMoon.reduce(
|
(s, i) => s + toNum(i.getCount),
|
0
|
);
|
const useTotal = this.raw.organMoon.reduce(
|
(s, i) => s + toNum(i.useCount),
|
0
|
);
|
return [
|
{
|
label: '总潜在捐献案例',
|
value: potential,
|
unit: '例',
|
icon: 'el-icon-s-flag',
|
gradient: 'linear-gradient(135deg,#1d4ed8,#38bdf8)'
|
},
|
{
|
label: '总完成捐献',
|
value: completed,
|
unit: '例',
|
icon: 'el-icon-circle-check',
|
gradient: 'linear-gradient(135deg,#059669,#34d399)'
|
},
|
{
|
label: '平均年龄',
|
value: age,
|
unit: '岁',
|
icon: 'el-icon-user',
|
decimals: 1,
|
gradient: 'linear-gradient(135deg,#7c3aed,#a78bfa)'
|
},
|
{
|
label: '器官获取总数',
|
value: getTotal,
|
unit: '个',
|
icon: 'el-icon-box',
|
gradient: 'linear-gradient(135deg,#0e7490,#22d3ee)'
|
},
|
{
|
label: '器官移植总数',
|
value: useTotal,
|
unit: '个',
|
icon: 'el-icon-s-help',
|
gradient: 'linear-gradient(135deg,#d97706,#fbbf24)'
|
}
|
];
|
},
|
gaugeCharts() {
|
return this.raw.gauges.map(g => ({ name: g.name, options: this.buildGauge(g) }));
|
},
|
trendOptions() {
|
const data = [];
|
this.raw.caseTrend.forEach(d => {
|
data.push({
|
time: this.fmtMonth(d.donateTime),
|
type: '潜在捐献',
|
value: toNum(d.potentialCount)
|
});
|
data.push({
|
time: this.fmtMonth(d.donateTime),
|
type: '完成捐献',
|
value: toNum(d.completeCount)
|
});
|
});
|
return {
|
data,
|
xField: 'time',
|
yField: 'value',
|
seriesField: 'type',
|
smooth: true,
|
color: ['#38a5f8', '#34d399'],
|
legend: legend(),
|
xAxis: xAxis(),
|
yAxis: yAxis(),
|
tooltip: tooltip(),
|
line: { size: 3 },
|
areaStyle: { fillOpacity: 0.16 }
|
};
|
},
|
organMoonOptions() {
|
const data = [];
|
this.raw.organMoon.forEach(d => {
|
data.push({
|
time: this.fmtMonth(d.donateTime),
|
type: '获取',
|
value: toNum(d.getCount)
|
});
|
data.push({
|
time: this.fmtMonth(d.donateTime),
|
type: '移植',
|
value: toNum(d.useCount)
|
});
|
});
|
return {
|
data,
|
xField: 'time',
|
yField: 'value',
|
seriesField: 'type',
|
smooth: true,
|
color: ['#22d3ee', '#fbbf24'],
|
lineStyle: { lineWidth: 3 },
|
point: { size: 4, shape: 'circle' },
|
legend: legend(),
|
xAxis: xAxis(),
|
yAxis: yAxis(),
|
tooltip: tooltip()
|
};
|
},
|
// 男女比例(按年龄段汇总为总体男女)
|
sexRatioOptions() {
|
let man = 0;
|
let woman = 0;
|
this.raw.caseSex.forEach(d => {
|
man += toNum(d.manCount);
|
woman += toNum(d.womanCount);
|
});
|
const data = [
|
{ type: '男', value: man },
|
{ type: '女', value: woman }
|
];
|
return {
|
data,
|
angleField: 'value',
|
colorField: 'type',
|
radius: 0.85,
|
innerRadius: 0.62,
|
color: ['#38a5f8', '#fb7185'],
|
label: {
|
type: 'spider',
|
content: '{percentage}',
|
style: { fill: '#c3cde4' }
|
},
|
legend: legend({ position: 'bottom' }),
|
tooltip: tooltip({ showTitle: false })
|
};
|
},
|
// 年龄与性别分布
|
ageSexOptions() {
|
const data = [];
|
this.raw.caseSex.forEach(d => {
|
const g = AGE_LABELS[d.ageGroup] || d.ageGroup;
|
data.push({ age: g, type: '男', value: toNum(d.manCount) });
|
data.push({ age: g, type: '女', value: toNum(d.womanCount) });
|
});
|
return {
|
data,
|
xField: 'age',
|
yField: 'value',
|
seriesField: 'type',
|
isGroup: true,
|
columnWidthRatio: 0.4,
|
color: ['#38a5f8', '#fb7185'],
|
legend: legend(),
|
xAxis: xAxis(),
|
yAxis: yAxis(),
|
tooltip: tooltip()
|
};
|
},
|
organTypeOptions() {
|
const data = [];
|
this.raw.organType.forEach(d => {
|
data.push({ organ: d.organName, type: '获取', value: toNum(d.getCount) });
|
data.push({ organ: d.organName, type: '移植', value: toNum(d.useCount) });
|
});
|
return {
|
data,
|
xField: 'value',
|
yField: 'organ',
|
seriesField: 'type',
|
isGroup: true,
|
barWidthRatio: 0.6,
|
color: ['#22d3ee', '#fbbf24'],
|
legend: legend(),
|
xAxis: yAxis(),
|
yAxis: xAxis(),
|
tooltip: tooltip()
|
};
|
},
|
// 器官弃用情况
|
discardOptions() {
|
const data = this.raw.organDiscard.map(d => ({
|
organ: d.organName,
|
value: toNum(d.discardCount)
|
}));
|
return {
|
data,
|
xField: 'organ',
|
yField: 'value',
|
columnWidthRatio: 0.5,
|
color: '#fb7185',
|
legend: false,
|
xAxis: xAxis({ label: { autoRotate: true } }),
|
yAxis: yAxis(),
|
tooltip: tooltip()
|
};
|
},
|
areaOptions() {
|
const data = [];
|
this.raw.caseTrendArea.forEach(d => {
|
const name = String(d.area || '').replace('地区', '');
|
data.push({
|
area: name,
|
type: '潜在捐献',
|
value: toNum(d.potentialCount)
|
});
|
data.push({
|
area: name,
|
type: '完成捐献',
|
value: toNum(d.completeCount)
|
});
|
});
|
return {
|
data,
|
xField: 'area',
|
yField: 'value',
|
seriesField: 'type',
|
isGroup: true,
|
columnWidthRatio: 0.45,
|
color: ['#38a5f8', '#34d399'],
|
legend: legend(),
|
xAxis: xAxis({ label: { autoRotate: true } }),
|
yAxis: yAxis(),
|
tooltip: tooltip()
|
};
|
},
|
carouselItems() {
|
return [
|
{ title: '男女比例', type: 'ring', options: this.sexRatioOptions },
|
{ title: '器官类型分布', type: 'bar', options: this.organTypeOptions },
|
{ title: '器官弃用情况', type: 'column', options: this.discardOptions },
|
{ title: '年龄与性别分布', type: 'column', options: this.ageSexOptions }
|
];
|
},
|
currentCarousel() {
|
return this.carouselItems[this.carouselIndex] || this.carouselItems[0];
|
}
|
},
|
created() {
|
this.startClock();
|
},
|
mounted() {
|
removeToken();
|
},
|
beforeDestroy() {
|
if (this.clockTimer) clearInterval(this.clockTimer);
|
if (this.carouselTimer) clearInterval(this.carouselTimer);
|
},
|
methods: {
|
fmtMonth(value) {
|
const m = toMonth(value);
|
if (!m) return '';
|
const mm = Number(m.split('-')[1]);
|
return mm ? `${mm}月` : m;
|
},
|
buildGauge(g) {
|
const pct = g.max ? Math.min(Math.max(g.value / g.max, 0), 1) : 0;
|
const suffix = g.max === 100 ? '%' : '';
|
return {
|
percent: pct,
|
startAngle: Math.PI,
|
endAngle: Math.PI * 2,
|
radius: 0.85,
|
innerRadius: 0.72,
|
range: { color: [g.color, '#1c2a4d'], width: 10 },
|
axis: {
|
label: null,
|
tickLine: { style: { stroke: '#2a3a63' } }
|
},
|
indicator: {
|
pointer: { style: { stroke: '#e6edff', lineWidth: 2 } },
|
pin: { style: { stroke: '#e6edff', lineWidth: 2, r: 4 } }
|
},
|
statistic: {
|
content: {
|
content: `${g.value}${suffix}`,
|
style: {
|
color: '#ffffff',
|
fontSize: 30,
|
fontWeight: 700,
|
lineHeight: 1,
|
opacity: 1
|
}
|
}
|
}
|
};
|
},
|
startClock() {
|
const tick = () => {
|
this.now = this.$moment().format('YYYY-MM-DD HH:mm:ss');
|
};
|
tick();
|
this.clockTimer = setInterval(tick, 1000);
|
},
|
startCarousel() {
|
if (this.carouselTimer) clearInterval(this.carouselTimer);
|
this.carouselTimer = setInterval(() => {
|
this.carouselIndex =
|
(this.carouselIndex + 1) % this.carouselItems.length;
|
}, 5000);
|
},
|
loginFn() {
|
if (!this.defaultAccount || !this.defaultPassword) {
|
this.$message.warning('请输入账号和密码');
|
return;
|
}
|
this.loginLoading = true;
|
login(encrypts(this.defaultAccount), encrypts(this.defaultPassword))
|
.then(res => {
|
if (res.code === 200) {
|
setToken(res.token);
|
this.dialogVisible = false;
|
this.$message.success('登录成功');
|
this.loadData();
|
} else {
|
this.$message.error(res.msg || '登录失败');
|
}
|
})
|
.catch(() => {
|
// 请求失败由 request 拦截器统一提示
|
})
|
.finally(() => {
|
this.loginLoading = false;
|
});
|
},
|
handleLogout() {
|
removeToken();
|
this.dialogVisible = true;
|
this.$message.info('已退出登录');
|
},
|
mapClick() {
|
// 区县点击可在此扩展下钻
|
},
|
async loadData() {
|
this.loading = true;
|
try {
|
// 后端接口就绪后,将下面模拟数据替换为真实接口:
|
// 趋势/性别/器官/地区/KPI → /opo/totals/*(@/api/businessApi/statistics.js)
|
// 区县地图 → getDonateorganDistrictDonateCalculate;
|
// 仪表盘 → getDonateorganQuality / getDonateorganPMP;
|
// 器官弃用 → 统计模块器官弃用相关接口(@/api/businessApi/statistics.js)
|
const m = mockCockpitData();
|
this.raw.mapData = m.mapData;
|
this.raw.gauges = m.gauges;
|
this.raw.caseTrend = m.caseTrend;
|
this.raw.organMoon = m.organMoon;
|
this.raw.caseBloodType = m.caseBloodType;
|
this.raw.caseSex = m.caseSex;
|
this.raw.organType = m.organType;
|
this.raw.organDiscard = m.organDiscard;
|
this.raw.caseTrendArea = m.caseTrendArea;
|
this.raw.totals = m.totals;
|
this.raw.caseAvgAge = m.caseAvgAge;
|
this.startCarousel();
|
} catch (e) {
|
console.error('驾驶舱数据加载失败', e);
|
} finally {
|
this.loading = false;
|
}
|
}
|
}
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.cockpit {
|
min-height: 100vh;
|
padding: 14px 16px 20px;
|
box-sizing: border-box;
|
color: #e6edff;
|
background: radial-gradient(
|
1200px 640px at 18% -10%,
|
#12275a 0%,
|
rgba(18, 39, 90, 0) 60%
|
),
|
radial-gradient(1000px 600px at 110% 10%, #0c2a52 0%, rgba(12, 42, 82, 0) 55%),
|
linear-gradient(180deg, #0a1226 0%, #070c1c 60%, #050812 100%);
|
}
|
::v-deep .el-loading-mask {
|
background-color: rgba(5, 8, 18, 0.75);
|
}
|
|
/* 顶部标题栏 */
|
.cockpit-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 6px 4px 14px;
|
.header-left {
|
display: flex;
|
align-items: center;
|
}
|
.header-logo {
|
width: 10px;
|
height: 26px;
|
border-radius: 3px;
|
margin-right: 12px;
|
background: linear-gradient(180deg, #38a5f8, #22d3ee);
|
box-shadow: 0 0 12px rgba(56, 165, 248, 0.7);
|
}
|
.header-title {
|
margin: 0;
|
font-size: 22px;
|
font-weight: 700;
|
letter-spacing: 2px;
|
background: linear-gradient(90deg, #eaf2ff, #6db8ff);
|
-webkit-background-clip: text;
|
background-clip: text;
|
-webkit-text-fill-color: transparent;
|
}
|
.header-right {
|
display: flex;
|
align-items: center;
|
}
|
.header-clock {
|
color: #9aa7c7;
|
font-size: 14px;
|
font-variant-numeric: tabular-nums;
|
letter-spacing: 1px;
|
margin-right: 16px;
|
}
|
.header-logout {
|
color: #9aa7c7;
|
&:hover {
|
color: #38a5f8;
|
}
|
}
|
}
|
|
/* KPI 指标卡 */
|
.kpi-row {
|
display: grid;
|
grid-template-columns: repeat(5, 1fr);
|
gap: 16px;
|
margin-bottom: 16px;
|
}
|
.kpi-card {
|
display: flex;
|
align-items: center;
|
padding: 12px 16px;
|
border-radius: 12px;
|
border: 1px solid rgba(90, 120, 190, 0.28);
|
background: linear-gradient(
|
135deg,
|
rgba(40, 52, 96, 0.72),
|
rgba(16, 24, 52, 0.9)
|
);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
}
|
.kpi-icon {
|
width: 44px;
|
height: 44px;
|
flex-shrink: 0;
|
border-radius: 10px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 22px;
|
color: #fff;
|
margin-right: 12px;
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
}
|
.kpi-body {
|
min-width: 0;
|
}
|
.kpi-label {
|
font-size: 13px;
|
color: #8aa0c8;
|
margin-bottom: 2px;
|
}
|
.kpi-value {
|
font-size: 24px;
|
font-weight: 700;
|
color: #fff;
|
line-height: 1.2;
|
}
|
.kpi-unit {
|
font-size: 13px;
|
font-weight: 400;
|
color: #8aa0c8;
|
margin-left: 4px;
|
}
|
|
/* 仪表盘行 */
|
.gauge-row {
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 16px;
|
margin-bottom: 16px;
|
}
|
|
/* 面板 */
|
.panel {
|
height: 100%;
|
padding: 14px 16px;
|
border-radius: 12px;
|
border: 1px solid rgba(80, 110, 180, 0.25);
|
background: linear-gradient(
|
180deg,
|
rgba(25, 35, 70, 0.82),
|
rgba(13, 20, 43, 0.82)
|
);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
box-sizing: border-box;
|
}
|
.panel-title {
|
position: relative;
|
font-size: 15px;
|
font-weight: 600;
|
color: #dbe4ff;
|
margin-bottom: 10px;
|
padding-left: 12px;
|
&::before {
|
content: '';
|
position: absolute;
|
left: 0;
|
top: 2px;
|
bottom: 2px;
|
width: 4px;
|
border-radius: 2px;
|
background: linear-gradient(180deg, #38a5f8, #22d3ee);
|
}
|
}
|
|
/* 左右两列堆叠 */
|
.col-stack {
|
display: flex;
|
flex-direction: column;
|
gap: 16px;
|
height: 100%;
|
.panel {
|
flex: 1;
|
}
|
}
|
|
/* 轮播 */
|
.panel--carousel {
|
position: relative;
|
}
|
.carousel-body {
|
height: 205px;
|
}
|
.carousel-dots {
|
position: absolute;
|
right: 16px;
|
top: 16px;
|
display: flex;
|
gap: 6px;
|
}
|
.dot {
|
width: 8px;
|
height: 8px;
|
border-radius: 50%;
|
background: rgba(120, 140, 190, 0.35);
|
cursor: pointer;
|
transition: all 0.3s;
|
&.active {
|
width: 20px;
|
border-radius: 4px;
|
background: #38a5f8;
|
}
|
}
|
.fade-enter-active,
|
.fade-leave-active {
|
transition: opacity 0.4s;
|
}
|
.fade-enter,
|
.fade-leave-to {
|
opacity: 0;
|
}
|
</style>
|
|
<!-- 登录弹窗通过 append-to-body 挂到 body 下,scoped 样式无法命中,故用全局样式 -->
|
<style lang="scss">
|
.el-dialog.login-dialog {
|
background: linear-gradient(180deg, rgba(28, 40, 78, 0.97), rgba(13, 20, 43, 0.99));
|
border: 1px solid rgba(74, 120, 200, 0.45);
|
border-radius: 14px;
|
box-shadow: 0 0 0 1px rgba(56, 165, 248, 0.12), 0 0 44px rgba(56, 165, 248, 0.28),
|
0 24px 60px rgba(0, 0, 0, 0.55);
|
overflow: hidden;
|
|
.el-dialog__header {
|
padding: 22px 26px 14px;
|
border-bottom: 1px solid rgba(74, 120, 200, 0.22);
|
}
|
.el-dialog__title {
|
color: #eaf2ff;
|
font-size: 18px;
|
font-weight: 600;
|
letter-spacing: 2px;
|
&::before {
|
content: '';
|
display: inline-block;
|
width: 4px;
|
height: 16px;
|
margin-right: 10px;
|
border-radius: 2px;
|
background: linear-gradient(180deg, #38a5f8, #22d3ee);
|
box-shadow: 0 0 8px rgba(56, 165, 248, 0.8);
|
vertical-align: -2px;
|
}
|
}
|
.el-dialog__body {
|
padding: 26px 26px 8px;
|
color: #c3cde4;
|
}
|
.el-dialog__footer {
|
padding: 8px 26px 24px;
|
text-align: right;
|
}
|
|
.el-form-item__label {
|
color: #aab6d4;
|
font-weight: 500;
|
letter-spacing: 1px;
|
}
|
.el-input__inner {
|
background: rgba(8, 15, 34, 0.9);
|
border: 1px solid rgba(90, 124, 200, 0.35);
|
border-radius: 6px;
|
color: #e6edff;
|
transition: border-color 0.25s, box-shadow 0.25s;
|
&::placeholder {
|
color: rgba(154, 167, 199, 0.6);
|
}
|
&:hover {
|
border-color: rgba(90, 124, 200, 0.55);
|
}
|
&:focus {
|
border-color: #38a5f8;
|
box-shadow: 0 0 0 3px rgba(56, 165, 248, 0.18);
|
}
|
}
|
|
.el-button {
|
background: rgba(24, 36, 68, 0.9);
|
border: 1px solid rgba(90, 124, 200, 0.4);
|
color: #c3cde4;
|
&:hover {
|
border-color: #38a5f8;
|
color: #eaf2ff;
|
}
|
}
|
.el-button--primary {
|
background: linear-gradient(135deg, #2563eb, #22d3ee);
|
border: none;
|
color: #fff;
|
box-shadow: 0 4px 14px rgba(56, 165, 248, 0.4);
|
&:hover,
|
&:focus {
|
background: linear-gradient(135deg, #2f6ff0, #2fd8f2);
|
color: #fff;
|
}
|
}
|
}
|
</style>
|