fix: 一些基本功能
This commit is contained in:
@@ -5,11 +5,12 @@ export namespace AuthApi {
|
|||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
password?: string;
|
password?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
export interface RefreshTokenResult {
|
||||||
@@ -22,7 +23,19 @@ export namespace AuthApi {
|
|||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult>('login', {
|
||||||
|
phone: data.username,
|
||||||
|
password: data.password,
|
||||||
|
code: data.code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
*/
|
||||||
|
export async function sendVerificationCode(username: string) {
|
||||||
|
return requestClient.post('send-verification-code', {
|
||||||
|
username,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +51,7 @@ export async function refreshTokenApi() {
|
|||||||
* 退出登录
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
export async function logoutApi() {
|
export async function logoutApi() {
|
||||||
return baseRequestClient.post('/auth/logout', {
|
return baseRequestClient.post('logout', {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -47,5 +60,5 @@ export async function logoutApi() {
|
|||||||
* 获取用户权限码
|
* 获取用户权限码
|
||||||
*/
|
*/
|
||||||
export async function getAccessCodesApi() {
|
export async function getAccessCodesApi() {
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
return requestClient.get<string[]>('auth/codes');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { $t } from '#/locales';
|
|||||||
|
|
||||||
const appName = computed(() => preferences.app.name);
|
const appName = computed(() => preferences.app.name);
|
||||||
const logo = computed(() => preferences.logo.source);
|
const logo = computed(() => preferences.logo.source);
|
||||||
|
// const logo = 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/logo.png?Expires=1734595037&OSSAccessKeyId=TMP.3KhpnCAjxkdTGWSj1ZCdYe3e9c6MBfEVZwN1CiHdUKUAT2D1mPEpACYgYMkUiQDVqy9Zqb7VqgUoh21W37FC3CztogHJPN&Signature=KmymeelO2OZxaAmvm2ACDK5UfF8%3D';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -16,6 +17,7 @@ const logo = computed(() => preferences.logo.source);
|
|||||||
:logo="logo"
|
:logo="logo"
|
||||||
:page-description="$t('authentication.pageDesc')"
|
:page-description="$t('authentication.pageDesc')"
|
||||||
:page-title="$t('authentication.pageTitle')"
|
:page-title="$t('authentication.pageTitle')"
|
||||||
|
:version="preferences.app.version"
|
||||||
>
|
>
|
||||||
<!-- 自定义工具栏 -->
|
<!-- 自定义工具栏 -->
|
||||||
<!-- <template #toolbar></template> -->
|
<!-- <template #toolbar></template> -->
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ref } from 'vue';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
import { useWatermark } from '@vben/hooks';
|
||||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
import { notification } from 'ant-design-vue';
|
import { notification } from 'ant-design-vue';
|
||||||
@@ -12,6 +13,8 @@ import { defineStore } from 'pinia';
|
|||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const { updateWatermark } = useWatermark();
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -23,20 +26,21 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
* 异步处理登录操作
|
* 异步处理登录操作
|
||||||
* Asynchronously handle the login process
|
* Asynchronously handle the login process
|
||||||
* @param params 登录表单数据
|
* @param params 登录表单数据
|
||||||
|
* @param onSuccess
|
||||||
*/
|
*/
|
||||||
async function authLogin(
|
async function authLogin(
|
||||||
params: Recordable<any>,
|
params: Recordable<any>,
|
||||||
onSuccess?: () => Promise<void> | void,
|
onSuccess?: () => Promise<void> | void,
|
||||||
) {
|
) {
|
||||||
// 异步处理用户登录操作并获取 accessToken
|
// 异步处理用户登录操作并获取 token
|
||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true;
|
loginLoading.value = true;
|
||||||
const { accessToken } = await loginApi(params);
|
const { token } = await loginApi(params);
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
// 如果成功获取到 token
|
||||||
if (accessToken) {
|
if (token) {
|
||||||
accessStore.setAccessToken(accessToken);
|
accessStore.setAccessToken(token);
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
// 获取用户信息并存储到 accessStore 中
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||||
@@ -54,12 +58,18 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
} else {
|
} else {
|
||||||
onSuccess
|
onSuccess
|
||||||
? await onSuccess?.()
|
? await onSuccess?.()
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
: await router.push(userInfo.home_path || DEFAULT_HOME_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
if (userInfo?.nick_name) {
|
||||||
|
// 这里修改水印内容
|
||||||
|
await updateWatermark({
|
||||||
|
contentType: 'multi-line-text',
|
||||||
|
// 水印内容
|
||||||
|
content: `${userInfo?.nick_name}\r\n${userInfo?.phone}`,
|
||||||
|
});
|
||||||
notification.success({
|
notification.success({
|
||||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nick_name}`,
|
||||||
duration: 3,
|
duration: 3,
|
||||||
message: $t('authentication.loginSuccess'),
|
message: $t('authentication.loginSuccess'),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { baseRequestClient, requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export namespace AuthApi {
|
|
||||||
/** 登录接口参数 */
|
|
||||||
export interface LoginParams {
|
|
||||||
password?: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
|
||||||
export interface LoginResult {
|
|
||||||
accessToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
|
||||||
data: string;
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录
|
|
||||||
*/
|
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新accessToken
|
|
||||||
*/
|
|
||||||
export async function refreshTokenApi() {
|
|
||||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*/
|
|
||||||
export async function logoutApi() {
|
|
||||||
return baseRequestClient.post('/auth/logout', {
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户权限码
|
|
||||||
*/
|
|
||||||
export async function getAccessCodesApi() {
|
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { useElementPlusDesignTokens } from '@vben/hooks';
|
|
||||||
|
|
||||||
import { ElConfigProvider } from 'element-plus';
|
|
||||||
|
|
||||||
import { elementLocale } from '#/locales';
|
|
||||||
|
|
||||||
defineOptions({ name: 'App' });
|
|
||||||
|
|
||||||
useElementPlusDesignTokens();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ElConfigProvider :locale="elementLocale">
|
|
||||||
<RouterView />
|
|
||||||
</ElConfigProvider>
|
|
||||||
</template>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import { AuthPageLayout } from '@vben/layouts';
|
|
||||||
import { preferences } from '@vben/preferences';
|
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const appName = computed(() => preferences.app.name);
|
|
||||||
const logo = computed(() => preferences.logo.source);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<AuthPageLayout
|
|
||||||
:app-name="appName"
|
|
||||||
:logo="logo"
|
|
||||||
:page-description="$t('authentication.pageDesc')"
|
|
||||||
:page-title="$t('authentication.pageTitle')"
|
|
||||||
>
|
|
||||||
<!-- 自定义工具栏 -->
|
|
||||||
<!-- <template #toolbar></template> -->
|
|
||||||
</AuthPageLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import type { Recordable, UserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
|
||||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import { ElNotification } from 'element-plus';
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const loginLoading = ref(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步处理登录操作
|
|
||||||
* Asynchronously handle the login process
|
|
||||||
* @param params 登录表单数据
|
|
||||||
*/
|
|
||||||
async function authLogin(
|
|
||||||
params: Recordable<any>,
|
|
||||||
onSuccess?: () => Promise<void> | void,
|
|
||||||
) {
|
|
||||||
// 异步处理用户登录操作并获取 accessToken
|
|
||||||
let userInfo: null | UserInfo = null;
|
|
||||||
try {
|
|
||||||
loginLoading.value = true;
|
|
||||||
const { accessToken } = await loginApi(params);
|
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
|
||||||
if (accessToken) {
|
|
||||||
// 将 accessToken 存储到 accessStore 中
|
|
||||||
accessStore.setAccessToken(accessToken);
|
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
|
||||||
fetchUserInfo(),
|
|
||||||
getAccessCodesApi(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
userInfo = fetchUserInfoResult;
|
|
||||||
|
|
||||||
userStore.setUserInfo(userInfo);
|
|
||||||
accessStore.setAccessCodes(accessCodes);
|
|
||||||
|
|
||||||
if (accessStore.loginExpired) {
|
|
||||||
accessStore.setLoginExpired(false);
|
|
||||||
} else {
|
|
||||||
onSuccess
|
|
||||||
? await onSuccess?.()
|
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
|
||||||
ElNotification({
|
|
||||||
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
|
||||||
title: $t('authentication.loginSuccess'),
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loginLoading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
userInfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
|
||||||
try {
|
|
||||||
await logoutApi();
|
|
||||||
} catch {
|
|
||||||
// 不做任何处理
|
|
||||||
}
|
|
||||||
resetAllStores();
|
|
||||||
accessStore.setLoginExpired(false);
|
|
||||||
|
|
||||||
// 回登录页带上当前路由地址
|
|
||||||
await router.replace({
|
|
||||||
path: LOGIN_PATH,
|
|
||||||
query: redirect
|
|
||||||
? {
|
|
||||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
|
||||||
let userInfo: null | UserInfo = null;
|
|
||||||
userInfo = await getUserInfoApi();
|
|
||||||
userStore.setUserInfo(userInfo);
|
|
||||||
return userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
function $reset() {
|
|
||||||
loginLoading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
$reset,
|
|
||||||
authLogin,
|
|
||||||
fetchUserInfo,
|
|
||||||
loginLoading,
|
|
||||||
logout,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
grid: {
|
|
||||||
bottom: 0,
|
|
||||||
containLabel: true,
|
|
||||||
left: '1%',
|
|
||||||
right: '1%',
|
|
||||||
top: '2 %',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
areaStyle: {},
|
|
||||||
data: [
|
|
||||||
111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,
|
|
||||||
36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,
|
|
||||||
111,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#5ab1ef',
|
|
||||||
},
|
|
||||||
smooth: true,
|
|
||||||
type: 'line',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
areaStyle: {},
|
|
||||||
data: [
|
|
||||||
33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,
|
|
||||||
11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#019680',
|
|
||||||
},
|
|
||||||
smooth: true,
|
|
||||||
type: 'line',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
axisPointer: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#019680',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trigger: 'axis',
|
|
||||||
},
|
|
||||||
// xAxis: {
|
|
||||||
// axisTick: {
|
|
||||||
// show: false,
|
|
||||||
// },
|
|
||||||
// boundaryGap: false,
|
|
||||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
// type: 'category',
|
|
||||||
// },
|
|
||||||
xAxis: {
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
boundaryGap: false,
|
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
type: 'category',
|
|
||||||
},
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
max: 80_000,
|
|
||||||
splitArea: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
splitNumber: 4,
|
|
||||||
type: 'value',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
legend: {
|
|
||||||
bottom: 0,
|
|
||||||
data: ['访问', '趋势'],
|
|
||||||
},
|
|
||||||
radar: {
|
|
||||||
indicator: [
|
|
||||||
{
|
|
||||||
name: '网页',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '移动端',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Ipad',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '客户端',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '第三方',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '其它',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
radius: '60%',
|
|
||||||
splitNumber: 8,
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
areaStyle: {
|
|
||||||
opacity: 1,
|
|
||||||
shadowBlur: 0,
|
|
||||||
shadowColor: 'rgba(0,0,0,.2)',
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 10,
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
itemStyle: {
|
|
||||||
color: '#b6a2de',
|
|
||||||
},
|
|
||||||
name: '访问',
|
|
||||||
value: [90, 50, 86, 40, 50, 20],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
itemStyle: {
|
|
||||||
color: '#5ab1ef',
|
|
||||||
},
|
|
||||||
name: '趋势',
|
|
||||||
value: [70, 75, 70, 76, 20, 85],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
// borderColor: '#fff',
|
|
||||||
borderRadius: 10,
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
symbolSize: 0,
|
|
||||||
type: 'radar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
animationDelay() {
|
|
||||||
return Math.random() * 400;
|
|
||||||
},
|
|
||||||
animationEasing: 'exponentialInOut',
|
|
||||||
animationType: 'scale',
|
|
||||||
center: ['50%', '50%'],
|
|
||||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
|
||||||
data: [
|
|
||||||
{ name: '外包', value: 500 },
|
|
||||||
{ name: '定制', value: 310 },
|
|
||||||
{ name: '技术支持', value: 274 },
|
|
||||||
{ name: '远程', value: 400 },
|
|
||||||
].sort((a, b) => {
|
|
||||||
return a.value - b.value;
|
|
||||||
}),
|
|
||||||
name: '商业占比',
|
|
||||||
radius: '80%',
|
|
||||||
roseType: 'radius',
|
|
||||||
type: 'pie',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
legend: {
|
|
||||||
bottom: '2%',
|
|
||||||
left: 'center',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
animationDelay() {
|
|
||||||
return Math.random() * 100;
|
|
||||||
},
|
|
||||||
animationEasing: 'exponentialInOut',
|
|
||||||
animationType: 'scale',
|
|
||||||
avoidLabelOverlap: false,
|
|
||||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
|
||||||
data: [
|
|
||||||
{ name: '搜索引擎', value: 1048 },
|
|
||||||
{ name: '直接访问', value: 735 },
|
|
||||||
{ name: '邮件营销', value: 580 },
|
|
||||||
{ name: '联盟广告', value: 484 },
|
|
||||||
],
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
fontSize: '12',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
// borderColor: '#fff',
|
|
||||||
borderRadius: 10,
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
position: 'center',
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
name: '访问来源',
|
|
||||||
radius: ['40%', '65%'],
|
|
||||||
type: 'pie',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
grid: {
|
|
||||||
bottom: 0,
|
|
||||||
containLabel: true,
|
|
||||||
left: '1%',
|
|
||||||
right: '1%',
|
|
||||||
top: '2 %',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
barMaxWidth: 80,
|
|
||||||
// color: '#4f69fd',
|
|
||||||
data: [
|
|
||||||
3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,
|
|
||||||
3200, 4800,
|
|
||||||
],
|
|
||||||
type: 'bar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
axisPointer: {
|
|
||||||
lineStyle: {
|
|
||||||
// color: '#4f69fd',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trigger: 'axis',
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),
|
|
||||||
type: 'category',
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
max: 8000,
|
|
||||||
splitNumber: 4,
|
|
||||||
type: 'value',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { baseRequestClient, requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export namespace AuthApi {
|
|
||||||
/** 登录接口参数 */
|
|
||||||
export interface LoginParams {
|
|
||||||
password?: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
|
||||||
export interface LoginResult {
|
|
||||||
accessToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
|
||||||
data: string;
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录
|
|
||||||
*/
|
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新accessToken
|
|
||||||
*/
|
|
||||||
export async function refreshTokenApi() {
|
|
||||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*/
|
|
||||||
export async function logoutApi() {
|
|
||||||
return baseRequestClient.post('/auth/logout', {
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户权限码
|
|
||||||
*/
|
|
||||||
export async function getAccessCodesApi() {
|
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { GlobalThemeOverrides } from 'naive-ui';
|
|
||||||
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import { useNaiveDesignTokens } from '@vben/hooks';
|
|
||||||
import { preferences } from '@vben/preferences';
|
|
||||||
|
|
||||||
import {
|
|
||||||
darkTheme,
|
|
||||||
dateEnUS,
|
|
||||||
dateZhCN,
|
|
||||||
enUS,
|
|
||||||
lightTheme,
|
|
||||||
NConfigProvider,
|
|
||||||
NMessageProvider,
|
|
||||||
NNotificationProvider,
|
|
||||||
zhCN,
|
|
||||||
} from 'naive-ui';
|
|
||||||
|
|
||||||
defineOptions({ name: 'App' });
|
|
||||||
|
|
||||||
const { commonTokens } = useNaiveDesignTokens();
|
|
||||||
|
|
||||||
const tokenLocale = computed(() =>
|
|
||||||
preferences.app.locale === 'zh-CN' ? zhCN : enUS,
|
|
||||||
);
|
|
||||||
const tokenDateLocale = computed(() =>
|
|
||||||
preferences.app.locale === 'zh-CN' ? dateZhCN : dateEnUS,
|
|
||||||
);
|
|
||||||
const tokenTheme = computed(() =>
|
|
||||||
preferences.theme.mode === 'dark' ? darkTheme : lightTheme,
|
|
||||||
);
|
|
||||||
|
|
||||||
const themeOverrides = computed((): GlobalThemeOverrides => {
|
|
||||||
return {
|
|
||||||
common: commonTokens,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NConfigProvider
|
|
||||||
:date-locale="tokenDateLocale"
|
|
||||||
:locale="tokenLocale"
|
|
||||||
:theme="tokenTheme"
|
|
||||||
:theme-overrides="themeOverrides"
|
|
||||||
class="h-full"
|
|
||||||
>
|
|
||||||
<NNotificationProvider>
|
|
||||||
<NMessageProvider>
|
|
||||||
<RouterView />
|
|
||||||
</NMessageProvider>
|
|
||||||
</NNotificationProvider>
|
|
||||||
</NConfigProvider>
|
|
||||||
</template>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import { AuthPageLayout } from '@vben/layouts';
|
|
||||||
import { preferences } from '@vben/preferences';
|
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const appName = computed(() => preferences.app.name);
|
|
||||||
const logo = computed(() => preferences.logo.source);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<AuthPageLayout
|
|
||||||
:app-name="appName"
|
|
||||||
:logo="logo"
|
|
||||||
:page-description="$t('authentication.pageDesc')"
|
|
||||||
:page-title="$t('authentication.pageTitle')"
|
|
||||||
>
|
|
||||||
<!-- 自定义工具栏 -->
|
|
||||||
<!-- <template #toolbar></template> -->
|
|
||||||
</AuthPageLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import type { Recordable, UserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
|
||||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import { notification } from '#/adapter/naive';
|
|
||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const loginLoading = ref(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步处理登录操作
|
|
||||||
* Asynchronously handle the login process
|
|
||||||
* @param params 登录表单数据
|
|
||||||
*/
|
|
||||||
async function authLogin(
|
|
||||||
params: Recordable<any>,
|
|
||||||
onSuccess?: () => Promise<void> | void,
|
|
||||||
) {
|
|
||||||
// 异步处理用户登录操作并获取 accessToken
|
|
||||||
let userInfo: null | UserInfo = null;
|
|
||||||
try {
|
|
||||||
loginLoading.value = true;
|
|
||||||
const { accessToken } = await loginApi(params);
|
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
|
||||||
if (accessToken) {
|
|
||||||
// 将 accessToken 存储到 accessStore 中
|
|
||||||
accessStore.setAccessToken(accessToken);
|
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
|
||||||
fetchUserInfo(),
|
|
||||||
getAccessCodesApi(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
userInfo = fetchUserInfoResult;
|
|
||||||
|
|
||||||
userStore.setUserInfo(userInfo);
|
|
||||||
accessStore.setAccessCodes(accessCodes);
|
|
||||||
|
|
||||||
if (accessStore.loginExpired) {
|
|
||||||
accessStore.setLoginExpired(false);
|
|
||||||
} else {
|
|
||||||
onSuccess
|
|
||||||
? await onSuccess?.()
|
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
|
||||||
notification.success({
|
|
||||||
content: $t('authentication.loginSuccess'),
|
|
||||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
|
||||||
duration: 3000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loginLoading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
userInfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
|
||||||
try {
|
|
||||||
await logoutApi();
|
|
||||||
} catch {
|
|
||||||
// 不做任何处理
|
|
||||||
}
|
|
||||||
resetAllStores();
|
|
||||||
accessStore.setLoginExpired(false);
|
|
||||||
|
|
||||||
// 回登录页带上当前路由地址
|
|
||||||
await router.replace({
|
|
||||||
path: LOGIN_PATH,
|
|
||||||
query: redirect
|
|
||||||
? {
|
|
||||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
|
||||||
let userInfo: null | UserInfo = null;
|
|
||||||
userInfo = await getUserInfoApi();
|
|
||||||
userStore.setUserInfo(userInfo);
|
|
||||||
return userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
function $reset() {
|
|
||||||
loginLoading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
$reset,
|
|
||||||
authLogin,
|
|
||||||
fetchUserInfo,
|
|
||||||
loginLoading,
|
|
||||||
logout,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
grid: {
|
|
||||||
bottom: 0,
|
|
||||||
containLabel: true,
|
|
||||||
left: '1%',
|
|
||||||
right: '1%',
|
|
||||||
top: '2 %',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
areaStyle: {},
|
|
||||||
data: [
|
|
||||||
111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,
|
|
||||||
36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,
|
|
||||||
111,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#5ab1ef',
|
|
||||||
},
|
|
||||||
smooth: true,
|
|
||||||
type: 'line',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
areaStyle: {},
|
|
||||||
data: [
|
|
||||||
33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,
|
|
||||||
11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#019680',
|
|
||||||
},
|
|
||||||
smooth: true,
|
|
||||||
type: 'line',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
axisPointer: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#019680',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trigger: 'axis',
|
|
||||||
},
|
|
||||||
// xAxis: {
|
|
||||||
// axisTick: {
|
|
||||||
// show: false,
|
|
||||||
// },
|
|
||||||
// boundaryGap: false,
|
|
||||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
// type: 'category',
|
|
||||||
// },
|
|
||||||
xAxis: {
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
boundaryGap: false,
|
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
type: 'category',
|
|
||||||
},
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
max: 80_000,
|
|
||||||
splitArea: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
splitNumber: 4,
|
|
||||||
type: 'value',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
legend: {
|
|
||||||
bottom: 0,
|
|
||||||
data: ['访问', '趋势'],
|
|
||||||
},
|
|
||||||
radar: {
|
|
||||||
indicator: [
|
|
||||||
{
|
|
||||||
name: '网页',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '移动端',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Ipad',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '客户端',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '第三方',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '其它',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
radius: '60%',
|
|
||||||
splitNumber: 8,
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
areaStyle: {
|
|
||||||
opacity: 1,
|
|
||||||
shadowBlur: 0,
|
|
||||||
shadowColor: 'rgba(0,0,0,.2)',
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowOffsetY: 10,
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
itemStyle: {
|
|
||||||
color: '#b6a2de',
|
|
||||||
},
|
|
||||||
name: '访问',
|
|
||||||
value: [90, 50, 86, 40, 50, 20],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
itemStyle: {
|
|
||||||
color: '#5ab1ef',
|
|
||||||
},
|
|
||||||
name: '趋势',
|
|
||||||
value: [70, 75, 70, 76, 20, 85],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
// borderColor: '#fff',
|
|
||||||
borderRadius: 10,
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
symbolSize: 0,
|
|
||||||
type: 'radar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
animationDelay() {
|
|
||||||
return Math.random() * 400;
|
|
||||||
},
|
|
||||||
animationEasing: 'exponentialInOut',
|
|
||||||
animationType: 'scale',
|
|
||||||
center: ['50%', '50%'],
|
|
||||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
|
||||||
data: [
|
|
||||||
{ name: '外包', value: 500 },
|
|
||||||
{ name: '定制', value: 310 },
|
|
||||||
{ name: '技术支持', value: 274 },
|
|
||||||
{ name: '远程', value: 400 },
|
|
||||||
].sort((a, b) => {
|
|
||||||
return a.value - b.value;
|
|
||||||
}),
|
|
||||||
name: '商业占比',
|
|
||||||
radius: '80%',
|
|
||||||
roseType: 'radius',
|
|
||||||
type: 'pie',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
legend: {
|
|
||||||
bottom: '2%',
|
|
||||||
left: 'center',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
animationDelay() {
|
|
||||||
return Math.random() * 100;
|
|
||||||
},
|
|
||||||
animationEasing: 'exponentialInOut',
|
|
||||||
animationType: 'scale',
|
|
||||||
avoidLabelOverlap: false,
|
|
||||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
|
||||||
data: [
|
|
||||||
{ name: '搜索引擎', value: 1048 },
|
|
||||||
{ name: '直接访问', value: 735 },
|
|
||||||
{ name: '邮件营销', value: 580 },
|
|
||||||
{ name: '联盟广告', value: 484 },
|
|
||||||
],
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
fontSize: '12',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
// borderColor: '#fff',
|
|
||||||
borderRadius: 10,
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
position: 'center',
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
name: '访问来源',
|
|
||||||
radius: ['40%', '65%'],
|
|
||||||
type: 'pie',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderEcharts({
|
|
||||||
grid: {
|
|
||||||
bottom: 0,
|
|
||||||
containLabel: true,
|
|
||||||
left: '1%',
|
|
||||||
right: '1%',
|
|
||||||
top: '2 %',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
barMaxWidth: 80,
|
|
||||||
// color: '#4f69fd',
|
|
||||||
data: [
|
|
||||||
3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,
|
|
||||||
3200, 4800,
|
|
||||||
],
|
|
||||||
type: 'bar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
axisPointer: {
|
|
||||||
lineStyle: {
|
|
||||||
// color: '#4f69fd',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trigger: 'axis',
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),
|
|
||||||
type: 'category',
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
max: 8000,
|
|
||||||
splitNumber: 4,
|
|
||||||
type: 'value',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" />
|
|
||||||
</template>
|
|
||||||
@@ -11,8 +11,11 @@ import {
|
|||||||
VbenIcon,
|
VbenIcon,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
import { desensitize } from '../../../../../../../apps/web-antd/src/util/tool';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
items: AnalysisOverviewItem[];
|
items: AnalysisOverviewItem[];
|
||||||
|
myCard: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -26,6 +29,25 @@ withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<Card v-if="myCard" class="bank-card w-full" title="我的银行卡">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle class="text-xl">{{ myCard?.bank_name }}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent class="card-content flex items-center justify-between">
|
||||||
|
<span class="card-number">
|
||||||
|
{{ desensitize(myCard?.bank_card, 'bankCard') }}
|
||||||
|
</span>
|
||||||
|
<VbenIcon
|
||||||
|
class="credit-card-icon size-8 flex-shrink-0"
|
||||||
|
icon="fluent-emoji-flat:credit-card"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter class="card-footer justify-between">
|
||||||
|
<span>{{ myCard?.bank_user_name }}</span>
|
||||||
|
<span>{{ myCard?.bank_account_type_txt }}</span>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
<template v-for="item in items" :key="item.title">
|
<template v-for="item in items" :key="item.title">
|
||||||
<Card :title="item.title" class="w-full">
|
<Card :title="item.title" class="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -34,8 +56,9 @@ withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
<CardContent class="flex items-center justify-between">
|
<CardContent class="flex items-center justify-between">
|
||||||
<VbenCountToAnimator
|
<VbenCountToAnimator
|
||||||
|
:decimals="3"
|
||||||
:end-val="item.value"
|
:end-val="item.value"
|
||||||
:start-val="1"
|
:start-val="0"
|
||||||
class="text-xl"
|
class="text-xl"
|
||||||
prefix=""
|
prefix=""
|
||||||
/>
|
/>
|
||||||
@@ -44,8 +67,9 @@ withDefaults(defineProps<Props>(), {
|
|||||||
<CardFooter class="justify-between">
|
<CardFooter class="justify-between">
|
||||||
<span>{{ item.totalTitle }}</span>
|
<span>{{ item.totalTitle }}</span>
|
||||||
<VbenCountToAnimator
|
<VbenCountToAnimator
|
||||||
|
:decimals="3"
|
||||||
:end-val="item.totalValue"
|
:end-val="item.totalValue"
|
||||||
:start-val="1"
|
:start-val="0"
|
||||||
prefix=""
|
prefix=""
|
||||||
/>
|
/>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
@@ -53,3 +77,71 @@ withDefaults(defineProps<Props>(), {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bank-card {
|
||||||
|
padding: 20px;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .bank-card {
|
||||||
|
padding: 20px;
|
||||||
|
color: #cd853f; /* 白色文字 */
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#3f3f3f 0%,
|
||||||
|
#1a1a1a 100%
|
||||||
|
); /* 深色渐变背景 */
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 8px rgb(0 0 0 / 60%); /* 更深的阴影 */
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: rgb(255 255 255 / 10%);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-number {
|
||||||
|
font-family:
|
||||||
|
ui-sans-serif,
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
'Helvetica Neue',
|
||||||
|
Arial,
|
||||||
|
'Noto Sans',
|
||||||
|
sans-serif,
|
||||||
|
'Apple Color Emoji',
|
||||||
|
'Segoe UI Emoji',
|
||||||
|
'Segoe UI Symbol',
|
||||||
|
'Noto Color Emoji' !important;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit-card-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
} else {
|
} else {
|
||||||
onSuccess
|
onSuccess
|
||||||
? await onSuccess?.()
|
? await onSuccess?.()
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
: await router.push(userInfo.home_path || DEFAULT_HOME_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
if (userInfo?.nick_name) {
|
||||||
notification.success({
|
notification.success({
|
||||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nick_name}`,
|
||||||
duration: 3,
|
duration: 3,
|
||||||
message: $t('authentication.loginSuccess'),
|
message: $t('authentication.loginSuccess'),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user