fix:移动和pc自适应菜单
This commit is contained in:
@@ -103,7 +103,7 @@ setupMixMenuContext();
|
|||||||
:sider-visible="siderVisible"
|
:sider-visible="siderVisible"
|
||||||
:sider-width="siderWidth"
|
:sider-width="siderWidth"
|
||||||
:sider-collapsed-width="siderCollapsedWidth"
|
:sider-collapsed-width="siderCollapsedWidth"
|
||||||
:footer-visible="themeStore.footer.visible"
|
:footer-visible="appStore.isMobile && themeStore.footer.visible"
|
||||||
:footer-height="themeStore.footer.height"
|
:footer-height="themeStore.footer.height"
|
||||||
:fixed-footer="themeStore.footer.fixed"
|
:fixed-footer="themeStore.footer.fixed"
|
||||||
:right-footer="themeStore.footer.right"
|
:right-footer="themeStore.footer.right"
|
||||||
@@ -120,7 +120,7 @@ setupMixMenuContext();
|
|||||||
<GlobalContent />
|
<GlobalContent />
|
||||||
<ThemeDrawer />
|
<ThemeDrawer />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<GlobalFooter />
|
<GlobalFooter v-if="appStore.isMobile" /> <!-- 修改这里 -->
|
||||||
</template>
|
</template>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {ProfileOutlined,UserOutlined,HomeOutlined} from "@ant-design/icons-vue";
|
||||||
|
import {useRouterPush} from "@/hooks/common";
|
||||||
|
|
||||||
|
const { routerPushByKey } = useRouterPush();
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'GlobalFooter'
|
name: 'GlobalFooter'
|
||||||
});
|
});
|
||||||
@@ -6,10 +10,34 @@ defineOptions({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DarkModeContainer class="h-full flex-center">
|
<DarkModeContainer class="h-full flex-center">
|
||||||
<a href="#" target="_blank" rel="noopener noreferrer">
|
<div class="flex-item">
|
||||||
Copyright © 2024 WANFi
|
<ButtonIcon class="text-icon-large" @click="routerPushByKey('home')">
|
||||||
</a>
|
<HomeOutlined />
|
||||||
|
</ButtonIcon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item">
|
||||||
|
<ButtonIcon>
|
||||||
|
<ProfileOutlined class="text-icon-large" @click="routerPushByKey('billing_billservice')"/>
|
||||||
|
</ButtonIcon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item">
|
||||||
|
<ButtonIcon class="text-icon-large" @click="routerPushByKey('user-info/usercard')">
|
||||||
|
<UserOutlined />
|
||||||
|
</ButtonIcon>
|
||||||
|
</div>
|
||||||
</DarkModeContainer>
|
</DarkModeContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.flex-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-item {
|
||||||
|
flex-basis: 33.33%; /* 每个子元素占据三分之一的宽度 */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* 在各自的空间内居中 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -52,10 +52,16 @@ const headerMenus = computed(() => {
|
|||||||
<DarkModeContainer class="h-full flex-y-center shadow-header">
|
<DarkModeContainer class="h-full flex-y-center shadow-header">
|
||||||
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
|
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
|
||||||
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
|
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
|
||||||
<div v-else class="h-full flex-y-center flex-1-hidden">
|
|
||||||
|
<!-- 只在非移动端显示菜单按钮 -->
|
||||||
|
<div v-if="!appStore.isMobile" class="h-full flex-y-center flex-1-hidden">
|
||||||
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
|
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
|
||||||
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
|
<GlobalBreadcrumb class="ml-12px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 在移动端时使用这个来保持布局 -->
|
||||||
|
<div v-else class="flex-1"></div>
|
||||||
|
|
||||||
<div class="h-full flex-y-center justify-end">
|
<div class="h-full flex-y-center justify-end">
|
||||||
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
|
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
|
||||||
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
|||||||
tab: {visible: false, cache: true, height: 44, mode: "chrome"},
|
tab: {visible: false, cache: true, height: 44, mode: "chrome"},
|
||||||
fixedHeaderAndTab: true,
|
fixedHeaderAndTab: true,
|
||||||
sider: {inverted: false, width: 220, collapsedWidth: 64, mixWidth: 90, mixCollapsedWidth: 64, mixChildMenuWidth: 200},
|
sider: {inverted: false, width: 220, collapsedWidth: 64, mixWidth: 90, mixCollapsedWidth: 64, mixChildMenuWidth: 200},
|
||||||
footer: {visible: true, fixed: false, height: 36, right: true}
|
footer: {visible: true, fixed: false, height: 50, right: true}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
import HeaderBanner from './modules/header-banner.vue';
|
import HeaderBanner from './modules/header-banner.vue';
|
||||||
import CardData from './modules/card-data.vue';
|
import CardData from './modules/card-data.vue';
|
||||||
import LineChart from './modules/line-chart.vue';
|
import LineChart from './modules/line-chart.vue';
|
||||||
import PieChart from './modules/pie-chart.vue';
|
// import PieChart from './modules/pie-chart.vue';
|
||||||
import ProjectNews from './modules/project-news.vue';
|
// import ProjectNews from './modules/project-news.vue';
|
||||||
import CreativityBanner from './modules/creativity-banner.vue';
|
// import CreativityBanner from './modules/creativity-banner.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ASpace direction="vertical" :size="16">
|
<ASpace direction="vertical" :size="16">
|
||||||
<HeaderBanner />
|
<HeaderBanner />
|
||||||
|
<LineChart />
|
||||||
<CardData />
|
<CardData />
|
||||||
<!-- <ARow :gutter="[16, 16]">-->
|
<!-- <ARow :gutter="[16, 16]">-->
|
||||||
<!-- <ACol :span="24" :lg="14">-->
|
<!-- <ACol :span="24" :lg="14">-->
|
||||||
<!-- <LineChart />-->
|
|
||||||
<!-- </ACol>-->
|
<!-- </ACol>-->
|
||||||
<!-- <ACol :span="24" :lg="10">-->
|
<!-- <ACol :span="24" :lg="10">-->
|
||||||
<!-- <PieChart />-->
|
<!-- <PieChart />-->
|
||||||
|
|||||||
@@ -1,151 +1,365 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from 'vue';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { $t } from '@/locales';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { fetchPackageList, submitPackageOrder } from '@/service/api/auth';
|
||||||
import { useEcharts } from '@/hooks/common/echarts';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
defineOptions({
|
const { t } = useI18n();
|
||||||
name: 'LineChart'
|
|
||||||
|
interface PackageOption {
|
||||||
|
id: string;
|
||||||
|
packageName: string;
|
||||||
|
price: number;
|
||||||
|
clientNum: number;
|
||||||
|
traffic: number;
|
||||||
|
trafficDisplay: string;
|
||||||
|
isRecommended?: boolean;
|
||||||
|
promotion?: string;
|
||||||
|
periodNum: number;
|
||||||
|
periodType: number;
|
||||||
|
validityPeriod: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加有效期类型枚举
|
||||||
|
const PERIOD_TYPE = {
|
||||||
|
HOUR: 0,
|
||||||
|
DAY: 1,
|
||||||
|
MONTH: 2,
|
||||||
|
YEAR: 3
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// 添加有效期单位映射
|
||||||
|
const PERIOD_UNIT = {
|
||||||
|
[PERIOD_TYPE.HOUR]: '小时',
|
||||||
|
[PERIOD_TYPE.DAY]: '天',
|
||||||
|
[PERIOD_TYPE.MONTH]: '月',
|
||||||
|
[PERIOD_TYPE.YEAR]: '年'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// 格式化有效期显示
|
||||||
|
const formatValidityPeriod = (num: number, type: number): string => {
|
||||||
|
const unit = PERIOD_UNIT[type as keyof typeof PERIOD_UNIT] || '未知';
|
||||||
|
return `${num}${unit}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 流量单位转换函数
|
||||||
|
const formatTraffic = (trafficKB: number): string => {
|
||||||
|
const KB_TO_MB = 1024;
|
||||||
|
const KB_TO_GB = 1024 * 1024;
|
||||||
|
const KB_TO_TB = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
if (trafficKB >= KB_TO_TB) {
|
||||||
|
// KB -> TB (除以 1024^3)
|
||||||
|
return `${(trafficKB / KB_TO_TB).toFixed(2)}TB`;
|
||||||
|
}
|
||||||
|
if (trafficKB >= KB_TO_GB) {
|
||||||
|
// KB -> GB (除以 1024^2)
|
||||||
|
return `${(trafficKB / KB_TO_GB).toFixed(2)}GB`;
|
||||||
|
}
|
||||||
|
if (trafficKB >= KB_TO_MB) {
|
||||||
|
// KB -> MB (除以 1024)
|
||||||
|
return `${(trafficKB / KB_TO_MB).toFixed(2)}MB`;
|
||||||
|
}
|
||||||
|
// 小于1MB的情况,保持KB单位
|
||||||
|
return `${trafficKB.toFixed(2)}KB`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const packageOptions = ref<PackageOption[]>([]);
|
||||||
|
const selectedPackage = ref<PackageOption>({
|
||||||
|
id: '1',
|
||||||
|
packageName: '',
|
||||||
|
price: 0,
|
||||||
|
clientNum: 0,
|
||||||
|
traffic: 0,
|
||||||
|
trafficDisplay: '0GB',
|
||||||
|
isRecommended: false,
|
||||||
|
promotion: '',
|
||||||
|
periodNum: 0,
|
||||||
|
periodType: PERIOD_TYPE.MONTH,
|
||||||
|
validityPeriod: '0月'
|
||||||
});
|
});
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const fetchPackages = async () => {
|
||||||
|
try {
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const response = await fetchPackageList();
|
||||||
tooltip: {
|
if (response.data && Array.isArray(response.data)) {
|
||||||
trigger: 'axis',
|
packageOptions.value = response.data.map(pkg => ({
|
||||||
axisPointer: {
|
id: pkg.id,
|
||||||
type: 'cross',
|
packageName: pkg.packageName,
|
||||||
label: {
|
price: parseFloat(pkg.price),
|
||||||
backgroundColor: '#6a7985'
|
clientNum: Number(pkg.clientNum),
|
||||||
}
|
traffic: Number(pkg.traffic),
|
||||||
}
|
trafficDisplay: formatTraffic(Number(pkg.traffic)),
|
||||||
},
|
isRecommended: pkg.isRecommended || false,
|
||||||
legend: {
|
promotion: pkg.promotion || '',
|
||||||
data: [$t('page.home.downloadCount'), $t('page.home.registerCount')]
|
periodNum: Number(pkg.periodNum),
|
||||||
},
|
periodType: Number(pkg.periodType),
|
||||||
grid: {
|
validityPeriod: formatValidityPeriod(Number(pkg.periodNum), Number(pkg.periodType))
|
||||||
left: '3%',
|
|
||||||
right: '4%',
|
|
||||||
bottom: '3%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
boundaryGap: false,
|
|
||||||
data: [] as string[]
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
color: '#8e9dff',
|
|
||||||
name: $t('page.home.downloadCount'),
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
stack: 'Total',
|
|
||||||
areaStyle: {
|
|
||||||
color: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{
|
|
||||||
offset: 0.25,
|
|
||||||
color: '#8e9dff'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: '#fff'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series'
|
|
||||||
},
|
|
||||||
data: [] as number[]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color: '#26deca',
|
|
||||||
name: $t('page.home.registerCount'),
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
stack: 'Total',
|
|
||||||
areaStyle: {
|
|
||||||
color: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{
|
|
||||||
offset: 0.25,
|
|
||||||
color: '#26deca'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: '#fff'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series'
|
|
||||||
},
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
async function mockData() {
|
if (packageOptions.value.length > 0) {
|
||||||
await new Promise(resolve => {
|
selectedPackage.value = packageOptions.value[0];
|
||||||
setTimeout(resolve, 1000);
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch packages:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectPackage = (option: PackageOption) => {
|
||||||
|
selectedPackage.value = option;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加办理套餐的方法
|
||||||
|
const handleSubmitOrder = async () => {
|
||||||
|
try {
|
||||||
|
await submitPackageOrder(selectedPackage.value.id);
|
||||||
|
message.success('套餐办理成功!');
|
||||||
|
} catch (error) {
|
||||||
|
message.error('套餐办理失败,请重试!');
|
||||||
|
console.error('Failed to submit order:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchPackages();
|
||||||
});
|
});
|
||||||
|
|
||||||
updateOptions(opts => {
|
|
||||||
opts.xAxis.data = ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00'];
|
|
||||||
opts.series[0].data = [4623, 6145, 6268, 6411, 1890, 4251, 2978, 3880, 3606, 4311];
|
|
||||||
opts.series[1].data = [2208, 2016, 2916, 4512, 8281, 2008, 1963, 2367, 2956, 678];
|
|
||||||
|
|
||||||
return opts;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLocale() {
|
|
||||||
updateOptions((opts, factory) => {
|
|
||||||
const originOpts = factory();
|
|
||||||
|
|
||||||
opts.legend.data = originOpts.legend.data;
|
|
||||||
opts.series[0].name = originOpts.series[0].name;
|
|
||||||
opts.series[1].name = originOpts.series[1].name;
|
|
||||||
|
|
||||||
return opts;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
mockData();
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => appStore.locale,
|
|
||||||
() => {
|
|
||||||
updateLocale();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// init
|
|
||||||
init();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ACard :bordered="false" class="card-wrapper">
|
<div class="package-container">
|
||||||
<div ref="domRef" class="h-360px overflow-hidden"></div>
|
<!-- 顶部价格展示 -->
|
||||||
</ACard>
|
<div class="price-header">
|
||||||
|
<div class="price">
|
||||||
|
<span class="currency">¥</span>
|
||||||
|
<span class="amount">{{ selectedPackage.price }}</span>
|
||||||
|
<span class="period">/月</span>
|
||||||
|
</div>
|
||||||
|
<div class="subtitle">{{ selectedPackage.packageName }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 套餐选项 -->
|
||||||
|
<div class="package-options">
|
||||||
|
<h3 class="section-title">{{ t('page.setmeal.changablelevel') }}</h3>
|
||||||
|
<div class="options-grid">
|
||||||
|
<div
|
||||||
|
v-for="option in packageOptions"
|
||||||
|
:key="option.id"
|
||||||
|
:class="[
|
||||||
|
'option-card',
|
||||||
|
{
|
||||||
|
recommended: option.isRecommended,
|
||||||
|
selected: selectedPackage.id === option.id
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
@click="selectPackage(option)"
|
||||||
|
>
|
||||||
|
<div v-if="option.isRecommended" class="recommended-tag">
|
||||||
|
{{ t('page.setmeal.highlyrecommended') }}
|
||||||
|
</div>
|
||||||
|
<div class="package-name">{{ option.packageName }}</div>
|
||||||
|
<div class="price">¥{{ option.price }}</div>
|
||||||
|
<div class="traffic">{{ option.trafficDisplay }}</div>
|
||||||
|
<div class="device-count">{{ option.clientNum }}台设备</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 套餐详情 -->
|
||||||
|
<div class="package-details">
|
||||||
|
<h3 class="section-title">{{ t('page.setmeal.mealdetail') }}</h3>
|
||||||
|
<div class="details-list">
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="label">套餐名称</div>
|
||||||
|
<div class="value">{{ selectedPackage.packageName }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="label">{{ t('page.setmeal.GeneralPurposeTraffic') }}</div>
|
||||||
|
<div class="value">{{ selectedPackage.trafficDisplay }},当月有效</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="label">设备数量</div>
|
||||||
|
<div class="value">最多{{ selectedPackage.clientNum }}台设备同时在线</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="label">有效期限</div>
|
||||||
|
<div class="value">{{ selectedPackage.validityPeriod }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<button
|
||||||
|
class="btn-primary"
|
||||||
|
@click="handleSubmitOrder"
|
||||||
|
:disabled="!selectedPackage.id"
|
||||||
|
>
|
||||||
|
{{ t('page.setmeal.Applynow') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.package-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 16px 16px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-header {
|
||||||
|
background: #fff1f0;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #333;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-card {
|
||||||
|
background: white;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-card.selected {
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
background: #fff1f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommended-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-details {
|
||||||
|
background: white;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 80px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: 1;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.highlight {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 32px;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traffic {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-count {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { UserOutlined, LockOutlined, SafetyCertificateOutlined, MobileOutlined, RightOutlined} from '@ant-design/icons-vue';
|
import { UserOutlined, LockOutlined, SafetyCertificateOutlined, MobileOutlined, RightOutlined, LinkOutlined, ApiOutlined, CalendarOutlined} from '@ant-design/icons-vue';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
@@ -42,7 +42,22 @@ const menuItems = [
|
|||||||
icon: MobileOutlined,
|
icon: MobileOutlined,
|
||||||
title: t('page.usercard.deviceconsole'),
|
title: t('page.usercard.deviceconsole'),
|
||||||
path: 'device'
|
path: 'device'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
icon: LinkOutlined,
|
||||||
|
title: t('page.usercard.access'),
|
||||||
|
path: 'access'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ApiOutlined,
|
||||||
|
title: t('page.usercard.records'),
|
||||||
|
path: 'records'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: CalendarOutlined,
|
||||||
|
title: t('page.usercard.cdrlrecords'),
|
||||||
|
path: 'cdrlrecords'
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleMenuClick = (path: string) => {
|
const handleMenuClick = (path: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user