init project
This commit is contained in:
commit
aeba86bb91
1
.env.production
Normal file
1
.env.production
Normal file
@ -0,0 +1 @@
|
||||
VITE_OA_BASEURL = https://oa.shiyuegame.com
|
1
.env.staging
Normal file
1
.env.staging
Normal file
@ -0,0 +1 @@
|
||||
VITE_OA_BASEURL = https://oa-pre.shiyue.com
|
25
.eslintrc.cjs
Normal file
25
.eslintrc.cjs
Normal file
@ -0,0 +1,25 @@
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
'extends': [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
rules: {
|
||||
semi: 2,
|
||||
'vue/multi-word-component-names': 0,
|
||||
indent: [
|
||||
2, 2, {
|
||||
SwitchCase: 1,
|
||||
},
|
||||
],
|
||||
'vue/html-indent': 2,
|
||||
'@typescript-eslint/comma-dangle': [2, 'always-multiline'],
|
||||
},
|
||||
};
|
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
localSet.js
|
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
20
components.d.ts
vendored
Normal file
20
components.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AFloatButton: typeof import('ant-design-vue/es')['FloatButton']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
}
|
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
8160
package-lock.json
generated
Normal file
8160
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "y-code",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --mode staging",
|
||||
"build:pre": "vite build --mode staging",
|
||||
"build": "vite build --mode production",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"ant-design-vue": "^4.1.2",
|
||||
"axios": "^1.6.7",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.15",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/node": "^20.11.10",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"less": "^4.2.0",
|
||||
"npm-run-all2": "^6.1.1",
|
||||
"typescript": "~5.3.0",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.0.11",
|
||||
"vue-tsc": "^1.8.27"
|
||||
}
|
||||
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
18
src/App.vue
Normal file
18
src/App.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||
import { legacyLogicalPropertiesTransformer, StyleProvider } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-config-provider :locale="zhCN" :transformers="[legacyLogicalPropertiesTransformer]">
|
||||
<StyleProvider hash-priority="high">
|
||||
<router-view />
|
||||
</StyleProvider>
|
||||
</a-config-provider>
|
||||
</template>
|
22
src/api/common.ts
Normal file
22
src/api/common.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { get } from '@/utils/request';
|
||||
|
||||
export interface UserInfoType {
|
||||
alias: string;
|
||||
all_dept_name: string;
|
||||
avatar: string;
|
||||
dept: string;
|
||||
dept_id: number;
|
||||
email: string;
|
||||
job: string;
|
||||
job_name: string;
|
||||
job_type: string;
|
||||
mobile: string;
|
||||
user_id: number;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export const getUserInfo = () => get<UserInfoType>({
|
||||
url: '/api/home/grade',
|
||||
});
|
||||
|
||||
export const logout = () => get({ url: '/api/common/logout' });
|
BIN
src/assets/avatar.png
Normal file
BIN
src/assets/avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
44
src/global.less
Normal file
44
src/global.less
Normal file
@ -0,0 +1,44 @@
|
||||
@primary-bg-color: #f8f8f8;
|
||||
|
||||
html, body {
|
||||
background-color: @primary-bg-color;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 选择滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px; /* 滚动条宽度 */
|
||||
}
|
||||
|
||||
/* 滚动条轨道 */
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 滚动条滑块 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块悬停 */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.normal-container {
|
||||
padding: 16px 24px;
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.mt-8 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
94
src/layout/components/Header.vue
Normal file
94
src/layout/components/Header.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { HomeOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
|
||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||
import avatar from '@/assets/avatar.png';
|
||||
import { OA_BASEURL } from '@/utils/request';
|
||||
import { logout } from '@/api/common';
|
||||
|
||||
const emits = defineEmits(['requestFullscreen']);
|
||||
|
||||
const route = useRoute();
|
||||
const userInfoStore = useUserInfoStore();
|
||||
|
||||
const handleLogout = () => {
|
||||
logout().then(() => {
|
||||
window.location.href = `${OA_BASEURL}/login?redirect=${encodeURIComponent(window.location.href)}`;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="root">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item v-for="item in route.matched" :key="item.path">
|
||||
<home-outlined v-if="item.path === '/' && item.name === 'layout'" />
|
||||
<span v-else>{{ item.meta.title || item.path }}</span>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
|
||||
<div class="user-area">
|
||||
<div class="fullscreen-icon-area" @click="emits('requestFullscreen')">
|
||||
<FullscreenOutlined class="fullscreen-icon" />
|
||||
</div>
|
||||
<a-dropdown
|
||||
placement="bottom"
|
||||
>
|
||||
<div style="display: flex;align-items: center;cursor: pointer;">
|
||||
<img :src="userInfoStore.userInfo?.avatar || avatar" class="avatar" />
|
||||
<div>{{ userInfoStore.userInfo?.alias || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleLogout">
|
||||
<span>退出登录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a class="back-oa" :href="`${OA_BASEURL}/front/`">
|
||||
返回OA
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 30px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 12px 0 rgba(0,0,0,.08);
|
||||
.user-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: rgb(51, 51, 51);
|
||||
.fullscreen-icon-area {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #ecf4fe;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
.fullscreen-icon {
|
||||
font-size: 20px;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
margin-left: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
59
src/layout/components/Sider.vue
Normal file
59
src/layout/components/Sider.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
function formatMemu(list: RouteType[], path: string = ''): ItemType[] {
|
||||
return list.filter((i) => i.isMenu).map((item) => {
|
||||
const key = item.path.startsWith('/') ? item.path : `${path}/${item.path}`;
|
||||
return {
|
||||
key,
|
||||
icon: item.icon,
|
||||
children: item.children.length ? formatMemu(item.children, key) : void 0,
|
||||
label: item.meta.title || '-',
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ItemType } from 'ant-design-vue';
|
||||
import routeList, { type RouteType } from '@/router/routes';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const selectedKeys = ref<string[]>(['1']);
|
||||
const openKeys = ref<string[]>(['sub1']);
|
||||
const menuList = computed(() => formatMemu(routeList[0].children));
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
watch(() => route.path, (val) => {
|
||||
if (!selectedKeys.value.includes(val)) {
|
||||
selectedKeys.value = [val];
|
||||
openKeys.value = route.matched.slice(1).map((i) => i.path);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const handleClick = (config: { key: string }) => {
|
||||
router.push(config.key);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-menu
|
||||
v-model:openKeys="openKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
mode="inline"
|
||||
:items="menuList"
|
||||
class="sider-root"
|
||||
@click="handleClick"
|
||||
v-bind="$attrs"
|
||||
></a-menu>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sider-root {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
border-inline-end: none !important;
|
||||
}
|
||||
</style>
|
112
src/layout/index.vue
Normal file
112
src/layout/index.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import Header from './components/Header.vue';
|
||||
import Sider from './components/Sider.vue';
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
|
||||
const userInfoStore = useUserInfoStore();
|
||||
const isCollapsed = ref(false);
|
||||
const isFullscreen = ref(false);
|
||||
const container = ref<HTMLDivElement>();
|
||||
|
||||
onMounted(() => {
|
||||
userInfoStore.fetchUserInfo();
|
||||
});
|
||||
|
||||
useEventListener(window, 'fullscreenchange', () => {
|
||||
isFullscreen.value = !!document.fullscreenElement;
|
||||
});
|
||||
|
||||
const handleFullscreen = () => {
|
||||
if (container.value) {
|
||||
container.value.requestFullscreen();
|
||||
}
|
||||
};
|
||||
const handleExitFullscreen = () => {
|
||||
document.exitFullscreen?.();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="root">
|
||||
<header class="header">
|
||||
<Header @requestFullscreen="handleFullscreen" />
|
||||
</header>
|
||||
<section class="left-aside" :class="{ 'left-aside-collapsed': isCollapsed }">
|
||||
<Sider :inlineCollapsed="isCollapsed" />
|
||||
<div class="collapsed-icon">
|
||||
<component :is="isCollapsed ? MenuUnfoldOutlined : MenuFoldOutlined" @click="isCollapsed = !isCollapsed" />
|
||||
</div>
|
||||
</section>
|
||||
<section class="container" :class="{ 'container-fullscreen': isFullscreen, 'container-collapsed': isCollapsed }" ref="container">
|
||||
<router-view />
|
||||
<a-float-button @click="handleExitFullscreen" v-if="isFullscreen">
|
||||
<template #icon>
|
||||
<FullscreenExitOutlined />
|
||||
</template>
|
||||
</a-float-button>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@header-height: 60px;
|
||||
@aside-width: 220px;
|
||||
@aside-width-collapsed: 60px;
|
||||
@header-margin: 12px;
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
.header {
|
||||
height: @header-height;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
}
|
||||
.left-aside {
|
||||
width: @aside-width;
|
||||
position: fixed;
|
||||
top: @header-height;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
height: calc(100% - @header-height - @header-margin);
|
||||
overflow-y: hidden;
|
||||
margin-top: @header-margin;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 12px 0 rgba(0,0,0,.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
transition: width 0.3s cubic-bezier(0.2, 0, 0, 1) 0s;
|
||||
.collapsed-icon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
padding-left: @aside-width + 8px;
|
||||
padding-top: @header-height + @header-margin;
|
||||
height: calc(100% - @header-height - @header-margin);
|
||||
position: relative;
|
||||
background-color: #f8f8f8;
|
||||
transition: padding 0.3s cubic-bezier(0.2, 0, 0, 1) 0s;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.container-collapsed {
|
||||
padding-left: @aside-width-collapsed + 8px;
|
||||
}
|
||||
.container-fullscreen {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(:where(.css-dev-only-do-not-override-1hsjdkk).ant-menu-inline-collapsed), .left-aside-collapsed {
|
||||
width: @aside-width-collapsed;
|
||||
}
|
||||
}
|
||||
</style>
|
12
src/main.ts
Normal file
12
src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import './global.less';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app');
|
8
src/router/guards.ts
Normal file
8
src/router/guards.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { NavigationGuardWithThis } from 'vue-router';
|
||||
|
||||
const titleGuard: NavigationGuardWithThis<undefined> = (to, from, next) => {
|
||||
next();
|
||||
document.title = to.meta.title ? `${to.meta.title} | 机制系统` : '机制系统';
|
||||
};
|
||||
|
||||
export { titleGuard };
|
13
src/router/index.ts
Normal file
13
src/router/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { titleGuard } from './guards';
|
||||
import routeList from './routes';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(''),
|
||||
routes: routeList,
|
||||
});
|
||||
|
||||
// 全局前置守卫
|
||||
router.beforeEach(titleGuard);
|
||||
|
||||
export default router;
|
71
src/router/routes.ts
Normal file
71
src/router/routes.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import Layout from '@/layout/index.vue';
|
||||
import { HomeOutlined, BarChartOutlined } from '@ant-design/icons-vue';
|
||||
import { h } from 'vue';
|
||||
import type { VNode, RendererNode, RendererElement } from 'vue';
|
||||
|
||||
export interface RouteType {
|
||||
path: string;
|
||||
name: string;
|
||||
component?: any;
|
||||
meta: { title?: string };
|
||||
isMenu?: boolean;
|
||||
redirect?: string;
|
||||
children: RouteType[];
|
||||
icon?: () => VNode<RendererNode, RendererElement, {
|
||||
[key: string]: any;
|
||||
}>
|
||||
}
|
||||
|
||||
const routeList: RouteType[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'layout',
|
||||
component: Layout,
|
||||
meta: { title: '首页' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: '-',
|
||||
meta: {},
|
||||
children: [],
|
||||
redirect: '/flow-manager/list',
|
||||
},
|
||||
{
|
||||
path: '/flow-manager',
|
||||
name: 'flow-manager',
|
||||
isMenu: true,
|
||||
meta: { title: '机制管理' },
|
||||
icon: () => h(HomeOutlined),
|
||||
children: [
|
||||
{
|
||||
path: 'list',
|
||||
name: 'list',
|
||||
component: () => import('@/views/flow-manager/list/index.vue'),
|
||||
meta: { title: '机制列表' },
|
||||
isMenu: true,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
name: 'create',
|
||||
component: () => import('@/views/flow-manager/create/index.vue'),
|
||||
meta: { title: '机制创建' },
|
||||
isMenu: true,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/data-overview',
|
||||
name: 'data-overview',
|
||||
isMenu: true,
|
||||
meta: { title: '数据总览' },
|
||||
children: [],
|
||||
icon: () => h(BarChartOutlined),
|
||||
component: () => import('@/views/data-overview/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routeList;
|
16
src/stores/useUserInfoStore.ts
Normal file
16
src/stores/useUserInfoStore.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { readonly, ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { getUserInfo } from '@/api/common';
|
||||
import type { UserInfoType } from '@/api/common';
|
||||
|
||||
export const useUserInfoStore = defineStore('userInfoStore', () => {
|
||||
const userInfo = ref<UserInfoType>();
|
||||
|
||||
const fetchUserInfo = () => {
|
||||
getUserInfo().then((res) => {
|
||||
userInfo.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
return { userInfo: readonly(userInfo), fetchUserInfo };
|
||||
});
|
102
src/utils/request.ts
Normal file
102
src/utils/request.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export interface ResopnseType<T> {
|
||||
reason: string
|
||||
message: string
|
||||
data: T
|
||||
ts: string
|
||||
}
|
||||
export const OA_BASEURL: string = import.meta.env.VITE_OA_BASEURL;
|
||||
|
||||
const requestType = {
|
||||
base: OA_BASEURL,
|
||||
};
|
||||
|
||||
const baseAxios: AxiosInstance = axios.create({
|
||||
baseURL: '',
|
||||
timeout: 100000,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
const errorHandle = (error: AxiosError) => {
|
||||
if (error.response) {
|
||||
const status = error.response?.status;
|
||||
switch (status) {
|
||||
case 401:
|
||||
message.warning('请先登录');
|
||||
window.location.href = `${OA_BASEURL}/login?redirect=${encodeURIComponent(window.location.href)}`;
|
||||
break;
|
||||
case 403:
|
||||
message.warning('权限不足');
|
||||
break;
|
||||
case 500:
|
||||
message.warning('服务器出错了…… (>_<)');
|
||||
break;
|
||||
default:
|
||||
message.warning('服务器出错了…… (>_<)');
|
||||
break;
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
message.error(error.message);
|
||||
return Promise.reject(error);
|
||||
};
|
||||
//响应拦截器
|
||||
baseAxios.interceptors.response.use((response: AxiosResponse) => {
|
||||
const { data, status } = response;
|
||||
|
||||
if (status !== 200) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
if (data.code) {
|
||||
if (data.code === 200) {
|
||||
return data;
|
||||
} else {
|
||||
message.warning(data.message);
|
||||
return Promise.reject(data);
|
||||
}
|
||||
}
|
||||
}, errorHandle);
|
||||
|
||||
type RequestConfig = Omit<AxiosRequestConfig, 'baseURL'> & { baseURL?: keyof typeof requestType }
|
||||
const request = <T = any>(config: RequestConfig) => {
|
||||
const host = requestType[config.baseURL || 'base'];
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
baseAxios
|
||||
.request<any, T>({ ...config, baseURL: host })
|
||||
.then((res: T) => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const get = <T = any>(config?: RequestConfig) => {
|
||||
return request<ResopnseType<T>>({
|
||||
...config,
|
||||
method: 'GET',
|
||||
});
|
||||
};
|
||||
|
||||
const post = <T = any>(config?: RequestConfig) =>
|
||||
request<ResopnseType<T>>({
|
||||
...config,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const put = <T = any>(config?: RequestConfig) =>
|
||||
request<ResopnseType<T>>({
|
||||
...config,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const del = <T = any>(config?: { url: string }) => request<ResopnseType<T>>({ ...config, method: 'DELETE' });
|
||||
|
||||
export { get, post, del, put, request };
|
3
src/views/data-overview/index.vue
Normal file
3
src/views/data-overview/index.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
data
|
||||
</template>
|
3
src/views/flow-manager/create/index.vue
Normal file
3
src/views/flow-manager/create/index.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
create
|
||||
</template>
|
8
src/views/flow-manager/list/index.vue
Normal file
8
src/views/flow-manager/list/index.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div class="normal-container">
|
||||
111
|
||||
</div>
|
||||
<div class="normal-container mt-16">
|
||||
222
|
||||
</div>
|
||||
</template>
|
15
tsconfig.app.json
Normal file
15
tsconfig.app.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.tsx", "components.d.ts", "*.d.ts"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
24
vite.config.ts
Normal file
24
vite.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
Components({
|
||||
resolvers: [AntDesignVueResolver({
|
||||
importStyle: 'less',
|
||||
})],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user