feat: 解决编译问题,增加一键部署脚本。
This commit is contained in:
@@ -141,6 +141,7 @@
|
||||
onCollapse={setCollapse}
|
||||
>
|
||||
{renderSubMenu()}
|
||||
// @ts-ignore
|
||||
</a-menu>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -45,8 +45,10 @@ export default function setupPermissionGuard(router: Router) {
|
||||
if (permissionsAllow) next();
|
||||
else {
|
||||
const destination =
|
||||
Permission.findFirstPermissionRoute(appRoutes, userStore.role) ||
|
||||
NOT_FOUND;
|
||||
Permission.findFirstPermissionRoute(
|
||||
appRoutes,
|
||||
userStore.permission
|
||||
) || NOT_FOUND;
|
||||
next(destination);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+228
@@ -0,0 +1,228 @@
|
||||
// 为Arco Design Vue组件提供更全面的类型定义
|
||||
import { DefineComponent } from 'vue';
|
||||
|
||||
// 通用属性类型,可以被Arco组件共享
|
||||
interface ArcoBaseProps {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// 定义各种Arco组件的类型接口
|
||||
export interface ArcoMenuProps extends ArcoBaseProps {
|
||||
'mode'?: 'horizontal' | 'vertical' | 'pop' | 'popButton';
|
||||
'collapsed'?: boolean;
|
||||
'openKeys'?: string[];
|
||||
'selectedKeys'?: string[];
|
||||
'autoOpen'?: boolean;
|
||||
'autoOpenSelected'?: boolean;
|
||||
'levelIndent'?: number;
|
||||
'showCollapseButton'?: boolean;
|
||||
'onCollapse'?: (collapsed: boolean) => void;
|
||||
'v-model:collapsed'?: boolean;
|
||||
'v-model:open-keys'?: string[];
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoSubMenuProps extends ArcoBaseProps {
|
||||
'key'?: string | number | symbol;
|
||||
'title'?: string | (() => unknown);
|
||||
'selectable'?: boolean;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoMenuItemProps extends ArcoBaseProps {
|
||||
'key'?: string | number | symbol;
|
||||
'disabled'?: boolean;
|
||||
'onClick'?: (event: Event) => void;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// 其他常用Arco组件类型定义
|
||||
export interface ArcoCardProps extends ArcoBaseProps {
|
||||
'title'?: string;
|
||||
'bordered'?: boolean;
|
||||
'hoverable'?: boolean;
|
||||
'loading'?: boolean;
|
||||
'size'?: 'small' | 'medium' | 'large';
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoButtonProps extends ArcoBaseProps {
|
||||
type?: 'primary' | 'secondary' | 'outline' | 'dashed' | 'text';
|
||||
status?: 'normal' | 'success' | 'warning' | 'danger';
|
||||
size?: 'mini' | 'small' | 'medium' | 'large';
|
||||
shape?: 'square' | 'circle' | 'round';
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
onClick?: (event: Event) => void;
|
||||
}
|
||||
|
||||
export interface ArcoLayoutProps extends ArcoBaseProps {
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoFormProps extends ArcoBaseProps {
|
||||
'model'?: Record<string, unknown>;
|
||||
'layout'?: 'horizontal' | 'vertical' | 'inline';
|
||||
'size'?: 'mini' | 'small' | 'medium' | 'large';
|
||||
'labelColProps'?: Record<string, unknown>;
|
||||
'wrapperColProps'?: Record<string, unknown>;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoFormItemProps extends ArcoBaseProps {
|
||||
'field'?: string;
|
||||
'label'?: string;
|
||||
'required'?: boolean;
|
||||
'rules'?: unknown[];
|
||||
'validateStatus'?: 'success' | 'error' | 'validating';
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoTableProps extends ArcoBaseProps {
|
||||
'columns'?: unknown[];
|
||||
'data'?: unknown[];
|
||||
'pagination'?: boolean | object;
|
||||
'loading'?: boolean;
|
||||
'rowKey'?: string | ((record: unknown) => string);
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoTableColumnProps extends ArcoBaseProps {
|
||||
'title'?: string;
|
||||
'dataIndex'?: string;
|
||||
'width'?: number | string;
|
||||
'align'?: 'left' | 'center' | 'right';
|
||||
'fixed'?: 'left' | 'right';
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoSelectProps extends ArcoBaseProps {
|
||||
'modelValue'?: unknown;
|
||||
'options'?: unknown[];
|
||||
'multiple'?: boolean;
|
||||
'disabled'?: boolean;
|
||||
'loading'?: boolean;
|
||||
'placeholder'?: string;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ArcoOptionProps extends ArcoBaseProps {
|
||||
'value'?: unknown;
|
||||
'label'?: string;
|
||||
'disabled'?: boolean;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// 声明全局JSX类型
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
// 基础组件
|
||||
'a-button': ArcoButtonProps;
|
||||
'a-card': ArcoCardProps;
|
||||
'a-menu': ArcoMenuProps;
|
||||
'a-menu-item': ArcoMenuItemProps;
|
||||
'a-sub-menu': ArcoSubMenuProps;
|
||||
|
||||
// 布局组件
|
||||
'a-layout': ArcoLayoutProps;
|
||||
'a-layout-header': ArcoBaseProps;
|
||||
'a-layout-content': ArcoBaseProps;
|
||||
'a-layout-footer': ArcoBaseProps;
|
||||
'a-layout-sider': ArcoBaseProps;
|
||||
'a-row': ArcoBaseProps;
|
||||
'a-col': ArcoBaseProps;
|
||||
'a-space': ArcoBaseProps;
|
||||
'a-divider': ArcoBaseProps;
|
||||
|
||||
// 表单组件
|
||||
'a-form': ArcoFormProps;
|
||||
'a-form-item': ArcoFormItemProps;
|
||||
'a-input': ArcoBaseProps;
|
||||
'a-textarea': ArcoBaseProps;
|
||||
'a-select': ArcoSelectProps;
|
||||
'a-option': ArcoOptionProps;
|
||||
'a-radio': ArcoBaseProps;
|
||||
'a-radio-group': ArcoBaseProps;
|
||||
'a-checkbox': ArcoBaseProps;
|
||||
'a-checkbox-group': ArcoBaseProps;
|
||||
'a-switch': ArcoBaseProps;
|
||||
|
||||
// 数据展示组件
|
||||
'a-table': ArcoTableProps;
|
||||
'a-table-column': ArcoTableColumnProps;
|
||||
'a-pagination': ArcoBaseProps;
|
||||
'a-tabs': ArcoBaseProps;
|
||||
'a-tab-pane': ArcoBaseProps;
|
||||
'a-dropdown': ArcoBaseProps;
|
||||
'a-dropdown-menu': ArcoBaseProps;
|
||||
'a-dropdown-item': ArcoBaseProps;
|
||||
'a-badge': ArcoBaseProps;
|
||||
'a-tag': ArcoBaseProps;
|
||||
|
||||
// 反馈组件
|
||||
'a-modal': ArcoBaseProps;
|
||||
'a-drawer': ArcoBaseProps;
|
||||
'a-alert': ArcoBaseProps;
|
||||
'a-spin': ArcoBaseProps;
|
||||
'a-progress': ArcoBaseProps;
|
||||
'a-result': ArcoBaseProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展Vue的类型定义
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
// 基础组件
|
||||
AButton: DefineComponent<ArcoButtonProps>;
|
||||
ACard: DefineComponent<ArcoCardProps>;
|
||||
AMenu: DefineComponent<ArcoMenuProps>;
|
||||
AMenuItem: DefineComponent<ArcoMenuItemProps>;
|
||||
ASubMenu: DefineComponent<ArcoSubMenuProps>;
|
||||
|
||||
// 布局组件
|
||||
ALayout: DefineComponent<ArcoLayoutProps>;
|
||||
ALayoutHeader: DefineComponent<ArcoBaseProps>;
|
||||
ALayoutContent: DefineComponent<ArcoBaseProps>;
|
||||
ALayoutFooter: DefineComponent<ArcoBaseProps>;
|
||||
ALayoutSider: DefineComponent<ArcoBaseProps>;
|
||||
ARow: DefineComponent<ArcoBaseProps>;
|
||||
ACol: DefineComponent<ArcoBaseProps>;
|
||||
ASpace: DefineComponent<ArcoBaseProps>;
|
||||
ADivider: DefineComponent<ArcoBaseProps>;
|
||||
|
||||
// 表单组件
|
||||
AForm: DefineComponent<ArcoFormProps>;
|
||||
AFormItem: DefineComponent<ArcoFormItemProps>;
|
||||
AInput: DefineComponent<ArcoBaseProps>;
|
||||
ATextarea: DefineComponent<ArcoBaseProps>;
|
||||
ASelect: DefineComponent<ArcoSelectProps>;
|
||||
AOption: DefineComponent<ArcoOptionProps>;
|
||||
ARadio: DefineComponent<ArcoBaseProps>;
|
||||
ARadioGroup: DefineComponent<ArcoBaseProps>;
|
||||
ACheckbox: DefineComponent<ArcoBaseProps>;
|
||||
ACheckboxGroup: DefineComponent<ArcoBaseProps>;
|
||||
ASwitch: DefineComponent<ArcoBaseProps>;
|
||||
|
||||
// 数据展示组件
|
||||
ATable: DefineComponent<ArcoTableProps>;
|
||||
ATableColumn: DefineComponent<ArcoTableColumnProps>;
|
||||
APagination: DefineComponent<ArcoBaseProps>;
|
||||
ATabs: DefineComponent<ArcoBaseProps>;
|
||||
ATabPane: DefineComponent<ArcoBaseProps>;
|
||||
ADropdown: DefineComponent<ArcoBaseProps>;
|
||||
ADropdownMenu: DefineComponent<ArcoBaseProps>;
|
||||
ADropdownItem: DefineComponent<ArcoBaseProps>;
|
||||
ABadge: DefineComponent<ArcoBaseProps>;
|
||||
ATag: DefineComponent<ArcoBaseProps>;
|
||||
|
||||
// 反馈组件
|
||||
AModal: DefineComponent<ArcoBaseProps>;
|
||||
ADrawer: DefineComponent<ArcoBaseProps>;
|
||||
AAlert: DefineComponent<ArcoBaseProps>;
|
||||
ASpin: DefineComponent<ArcoBaseProps>;
|
||||
AProgress: DefineComponent<ArcoBaseProps>;
|
||||
AResult: DefineComponent<ArcoBaseProps>;
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
// 为了支持Vue JSX和Arco Design Vue组件,我们需要适当扩展类型声明
|
||||
|
||||
// 引入Vue运行时核心类型
|
||||
import 'vue';
|
||||
|
||||
// 为Arco Design Vue组件定义基础接口
|
||||
interface ArcoMenuProps {
|
||||
'mode'?: 'horizontal' | 'vertical' | 'pop' | 'popButton';
|
||||
'collapsed'?: boolean;
|
||||
'openKeys'?: string[];
|
||||
'selectedKeys'?: string[];
|
||||
'autoOpen'?: boolean;
|
||||
'autoOpenSelected'?: boolean;
|
||||
'levelIndent'?: number;
|
||||
'showCollapseButton'?: boolean;
|
||||
'onCollapse'?: (collapsed: boolean) => void;
|
||||
'v-model:collapsed'?: boolean;
|
||||
'v-model:open-keys'?: string[];
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ArcoSubMenuProps {
|
||||
'key'?: string | number;
|
||||
'title'?: string | (() => unknown);
|
||||
'selectable'?: boolean;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ArcoMenuItemProps {
|
||||
'key'?: string | number;
|
||||
'disabled'?: boolean;
|
||||
'onClick'?: (event: Event) => void;
|
||||
'v-slots'?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// 扩展Vue的类型定义
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AMenu: ArcoMenuProps;
|
||||
AMenuItem: ArcoMenuItemProps;
|
||||
ASubMenu: ArcoSubMenuProps;
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展全局JSX声明
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'a-menu': ArcoMenuProps;
|
||||
'a-menu-item': ArcoMenuItemProps;
|
||||
'a-sub-menu': ArcoSubMenuProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, onBeforeMount } from 'vue';
|
||||
import { useRoute, useRouter, LocationQueryRaw } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
onBeforeMount(() => {
|
||||
const { params, query } = route;
|
||||
const { path } = params;
|
||||
|
||||
router.replace({
|
||||
path: `/${Array.isArray(path) ? path.join('/') : path}`,
|
||||
query: query as LocationQueryRaw,
|
||||
});
|
||||
});
|
||||
|
||||
return () => null;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="error-container">
|
||||
<a-result
|
||||
status="error"
|
||||
title="操作失败"
|
||||
sub-title="很抱歉,您的操作未能完成,请检查并重试"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button key="retry" type="primary">重试</a-button>
|
||||
<a-button key="back" @click="back">返回上一页</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResultError',
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
|
||||
const back = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
return {
|
||||
back,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.error-container {
|
||||
padding: 24px;
|
||||
background-color: var(--color-bg-2);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="success-container">
|
||||
<a-result
|
||||
status="success"
|
||||
title="操作成功"
|
||||
sub-title="操作已成功完成,您可以继续其他操作或返回首页"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button key="home" type="primary" @click="toHome"
|
||||
>返回首页</a-button
|
||||
>
|
||||
<a-button key="back" @click="back">返回上一页</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResultSuccess',
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
|
||||
const toHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
return {
|
||||
toHome,
|
||||
back,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.success-container {
|
||||
padding: 24px;
|
||||
background-color: var(--color-bg-2);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-card class="general-card" title="个人信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-card class="user-card">
|
||||
<template #cover>
|
||||
<div class="avatar-wrapper">
|
||||
<a-avatar :size="84" :src="userInfo.avatar || '/avatar.jpg'" />
|
||||
</div>
|
||||
</template>
|
||||
<a-card-meta :title="userInfo.name">
|
||||
<template #description>
|
||||
<div class="user-info">
|
||||
<div>邮箱: {{ userInfo.email || '未设置' }}</div>
|
||||
<div>职位: {{ userInfo.jobName || '未设置' }}</div>
|
||||
<div>部门: {{ userInfo.organizationName || '未设置' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-descriptions :column="1" size="large" bordered>
|
||||
<a-descriptions-item label="用户名">{{
|
||||
userInfo.name || '未设置'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系电话">{{
|
||||
userInfo.phone || '未设置'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="邮箱地址">{{
|
||||
userInfo.email || '未设置'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="所在地区">{{
|
||||
userInfo.locationName || '未设置'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册时间">{{
|
||||
userInfo.registrationDate || '未设置'
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive } from 'vue';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserInfo',
|
||||
setup() {
|
||||
const userStore = useUserStore();
|
||||
|
||||
const userInfo = reactive({
|
||||
...userStore.$state,
|
||||
});
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 20px;
|
||||
|
||||
.general-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
text-align: center;
|
||||
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
background-color: var(--color-bg-2);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-top: 12px;
|
||||
div {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-card class="general-card" title="个人设置">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" title="基本信息">
|
||||
<a-form
|
||||
:model="userForm"
|
||||
label-align="right"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 18 }"
|
||||
>
|
||||
<a-form-item field="avatar" label="头像">
|
||||
<div class="avatar-uploader">
|
||||
<a-avatar :size="64" :src="userForm.avatar || '/avatar.jpg'" />
|
||||
<div class="upload-actions">
|
||||
<a-button type="primary" size="small">更换头像</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item field="name" label="用户名">
|
||||
<a-input v-model="userForm.name" placeholder="请输入用户名" />
|
||||
</a-form-item>
|
||||
<a-form-item field="email" label="邮箱">
|
||||
<a-input v-model="userForm.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
<a-form-item field="phone" label="电话号码">
|
||||
<a-input v-model="userForm.phone" placeholder="请输入电话号码" />
|
||||
</a-form-item>
|
||||
<a-form-item field="introduction" label="个人简介">
|
||||
<a-textarea
|
||||
v-model="userForm.introduction"
|
||||
placeholder="请输入个人简介"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col-props="{ offset: 6 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="saveSettings">保存</a-button>
|
||||
<a-button @click="resetForm">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" title="安全设置">
|
||||
<a-space direction="vertical" fill style="width: 100%">
|
||||
<a-card>
|
||||
<template #title>修改密码</template>
|
||||
<div class="password-item">
|
||||
<div class="password-item-title">账户密码</div>
|
||||
<div class="password-item-action">
|
||||
<a-button>修改</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-card>
|
||||
<template #title>安全认证</template>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item-title">
|
||||
<div>两步验证</div>
|
||||
<div class="setting-item-desc"
|
||||
>使用两步验证提高账户安全性</div
|
||||
>
|
||||
</div>
|
||||
<div class="setting-item-action">
|
||||
<a-switch />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive } from 'vue';
|
||||
import { useUserStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserSetting',
|
||||
setup() {
|
||||
const userStore = useUserStore();
|
||||
|
||||
const userForm = reactive({
|
||||
...userStore.$state,
|
||||
});
|
||||
|
||||
const originalData = { ...userForm };
|
||||
|
||||
const saveSettings = () => {
|
||||
userStore.setInfo({
|
||||
name: userForm.name,
|
||||
email: userForm.email,
|
||||
phone: userForm.phone,
|
||||
introduction: userForm.introduction,
|
||||
});
|
||||
Message.success('设置已保存');
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
Object.assign(userForm, originalData);
|
||||
Message.info('设置已重置');
|
||||
};
|
||||
|
||||
return {
|
||||
userForm,
|
||||
saveSettings,
|
||||
resetForm,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 20px;
|
||||
|
||||
.general-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.avatar-uploader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.password-item,
|
||||
.setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
|
||||
.password-item-title,
|
||||
.setting-item-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.setting-item-desc {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-3);
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-card class="general-card" title="数据分析">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24" :lg="16">
|
||||
<!-- 主要图表区域 -->
|
||||
<a-card class="content-card">
|
||||
<template #title>销售趋势分析</template>
|
||||
<div class="chart-wrapper">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="24" :lg="8">
|
||||
<!-- 数据统计卡片 -->
|
||||
<a-space direction="vertical" fill style="width: 100%">
|
||||
<a-card class="content-card">
|
||||
<a-statistic
|
||||
title="总销售额"
|
||||
:value="12345678"
|
||||
:precision="2"
|
||||
show-group-separator
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-thumb-up />
|
||||
</template>
|
||||
</a-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item">
|
||||
<span>日环比</span>
|
||||
<span class="green">
|
||||
<icon-arrow-rise />
|
||||
10.5%
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-item">
|
||||
<span>周环比</span>
|
||||
<span class="green">
|
||||
<icon-arrow-rise />
|
||||
12.8%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-card class="content-card">
|
||||
<a-statistic
|
||||
title="访问量"
|
||||
:value="8846"
|
||||
:precision="0"
|
||||
show-group-separator
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-user />
|
||||
</template>
|
||||
</a-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item">
|
||||
<span>日环比</span>
|
||||
<span class="red">
|
||||
<icon-arrow-fall />
|
||||
2.5%
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-item">
|
||||
<span>周环比</span>
|
||||
<span class="green">
|
||||
<icon-arrow-rise />
|
||||
15.2%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-card class="content-card">
|
||||
<template #title>销售数据详情</template>
|
||||
<a-table :data="tableData" :pagination="{ pageSize: 5 }">
|
||||
<template #columns>
|
||||
<a-table-column title="ID" data-index="id" />
|
||||
<a-table-column title="商品名称" data-index="name" />
|
||||
<a-table-column title="销售额" data-index="sales">
|
||||
<template #cell="{ record }">{{
|
||||
record.sales.toLocaleString()
|
||||
}}</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="销售量" data-index="count" />
|
||||
<a-table-column title="日期" data-index="date" />
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataAnalysis',
|
||||
setup() {
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
const chart = ref<echarts.ECharts | null>(null);
|
||||
|
||||
const tableData = [
|
||||
{ id: 1, name: '商品A', sales: 12345, count: 123, date: '2023-01-01' },
|
||||
{ id: 2, name: '商品B', sales: 23456, count: 234, date: '2023-01-02' },
|
||||
{ id: 3, name: '商品C', sales: 34567, count: 345, date: '2023-01-03' },
|
||||
{ id: 4, name: '商品D', sales: 45678, count: 456, date: '2023-01-04' },
|
||||
{ id: 5, name: '商品E', sales: 56789, count: 567, date: '2023-01-05' },
|
||||
{ id: 6, name: '商品F', sales: 67890, count: 678, date: '2023-01-06' },
|
||||
{ id: 7, name: '商品G', sales: 78901, count: 789, date: '2023-01-07' },
|
||||
];
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chart.value = echarts.init(chartRef.value);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
legend: {
|
||||
data: ['销售额', '访问量'],
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '销售额',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [120, 132, 101, 134, 90, 230, 210],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#1890ff',
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(24,144,255,0.3)' },
|
||||
{ offset: 1, color: 'rgba(24,144,255,0.1)' },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [220, 182, 191, 234, 290, 330, 310],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#52c41a',
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(82,196,26,0.3)' },
|
||||
{ offset: 1, color: 'rgba(82,196,26,0.1)' },
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
chart.value.setOption(option);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart.value?.resize();
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
chartRef,
|
||||
tableData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 20px;
|
||||
|
||||
.general-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
height: 360px;
|
||||
|
||||
.chart {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.statistic-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
|
||||
.footer-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
span:first-child {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
span:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #f5222d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-card class="general-card" title="多维数据分析">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<!-- 图表切换控制 -->
|
||||
<div class="chart-control">
|
||||
<a-radio-group v-model="chartType" type="button">
|
||||
<a-radio value="bar">柱状图</a-radio>
|
||||
<a-radio value="line">折线图</a-radio>
|
||||
<a-radio value="pie">饼图</a-radio>
|
||||
</a-radio-group>
|
||||
<a-select v-model="timeRange" style="width: 120px">
|
||||
<a-option value="week">本周</a-option>
|
||||
<a-option value="month">本月</a-option>
|
||||
<a-option value="year">本年</a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div class="chart-wrapper">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<!-- 数据维度选择 -->
|
||||
<a-card class="content-card">
|
||||
<template #title>数据维度配置</template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<a-form-item field="dimension1" label="维度1">
|
||||
<a-select v-model="formData.dimension1">
|
||||
<a-option value="sales">销售额</a-option>
|
||||
<a-option value="traffic">流量</a-option>
|
||||
<a-option value="users">用户数</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="dimension2" label="维度2">
|
||||
<a-select v-model="formData.dimension2">
|
||||
<a-option value="region">地区</a-option>
|
||||
<a-option value="age">年龄段</a-option>
|
||||
<a-option value="gender">性别</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<a-form-item field="dimension3" label="维度3">
|
||||
<a-select v-model="formData.dimension3">
|
||||
<a-option value="channel">渠道</a-option>
|
||||
<a-option value="product">商品</a-option>
|
||||
<a-option value="time">时间</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="updateChart"
|
||||
>更新图表</a-button
|
||||
>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-card class="content-card">
|
||||
<template #title>多维数据详情</template>
|
||||
<a-table :data="tableData" :pagination="{ pageSize: 5 }">
|
||||
<template #columns>
|
||||
<a-table-column title="ID" data-index="id" />
|
||||
<a-table-column title="维度1" data-index="dim1" />
|
||||
<a-table-column title="维度2" data-index="dim2" />
|
||||
<a-table-column title="维度3" data-index="dim3" />
|
||||
<a-table-column title="数值" data-index="value">
|
||||
<template #cell="{ record }">{{
|
||||
record.value.toLocaleString()
|
||||
}}</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, reactive, watch } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MultiDimensionDataAnalysis',
|
||||
setup() {
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
const chart = ref<echarts.ECharts | null>(null);
|
||||
|
||||
const chartType = ref('bar');
|
||||
const timeRange = ref('month');
|
||||
|
||||
const formData = reactive({
|
||||
dimension1: 'sales',
|
||||
dimension2: 'region',
|
||||
dimension3: 'channel',
|
||||
});
|
||||
|
||||
// 模拟数据
|
||||
const tableData = ref([
|
||||
{ id: 1, dim1: '销售额', dim2: '华东', dim3: '线上', value: 4332 },
|
||||
{ id: 2, dim1: '销售额', dim2: '华南', dim3: '线上', value: 2986 },
|
||||
{ id: 3, dim1: '销售额', dim2: '华北', dim3: '线上', value: 3219 },
|
||||
{ id: 4, dim1: '销售额', dim2: '华东', dim3: '线下', value: 3721 },
|
||||
{ id: 5, dim1: '销售额', dim2: '华南', dim3: '线下', value: 2413 },
|
||||
{ id: 6, dim1: '销售额', dim2: '华北', dim3: '线下', value: 1893 },
|
||||
{ id: 7, dim1: '流量', dim2: '华东', dim3: '线上', value: 10243 },
|
||||
{ id: 8, dim1: '流量', dim2: '华南', dim3: '线上', value: 8752 },
|
||||
]);
|
||||
|
||||
const getChartData = () => {
|
||||
// 模拟根据维度和时间范围获取不同的数据
|
||||
// 实际应用中可以通过API获取
|
||||
return {
|
||||
xAxis: ['华东', '华南', '华北', '西南', '西北', '东北'],
|
||||
series: [
|
||||
{
|
||||
name: '线上',
|
||||
data: [4332, 2986, 3219, 2851, 1845, 2016],
|
||||
},
|
||||
{
|
||||
name: '线下',
|
||||
data: [3721, 2413, 1893, 2173, 1634, 1723],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const updateChart = () => {
|
||||
if (!chart.value) return;
|
||||
|
||||
const chartData = getChartData();
|
||||
|
||||
let option: echarts.EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: ['线上', '线下'],
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (chartType.value === 'bar') {
|
||||
option = {
|
||||
...option,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.xAxis,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: chartData.series.map((item) => ({
|
||||
name: item.name,
|
||||
type: 'bar',
|
||||
data: item.data,
|
||||
})),
|
||||
};
|
||||
} else if (chartType.value === 'line') {
|
||||
option = {
|
||||
...option,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.xAxis,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: chartData.series.map((item) => ({
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: item.data,
|
||||
})),
|
||||
};
|
||||
} else if (chartType.value === 'pie') {
|
||||
// 将数据转换为饼图格式
|
||||
const pieData = chartData.xAxis.map((region, index) => ({
|
||||
name: region,
|
||||
value:
|
||||
chartData.series[0].data[index] + chartData.series[1].data[index],
|
||||
}));
|
||||
|
||||
option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: chartData.xAxis,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: formData.dimension1,
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
center: ['50%', '60%'],
|
||||
data: pieData,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
chart.value.setOption(option, true);
|
||||
};
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chart.value = echarts.init(chartRef.value);
|
||||
updateChart();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart.value?.resize();
|
||||
});
|
||||
};
|
||||
|
||||
watch([chartType, timeRange], () => {
|
||||
updateChart();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
});
|
||||
|
||||
return {
|
||||
chartRef,
|
||||
chartType,
|
||||
timeRange,
|
||||
formData,
|
||||
tableData,
|
||||
updateChart,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 20px;
|
||||
|
||||
.general-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-control {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
height: 400px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.chart {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user