Commit 9e1b4305 authored by sunguoshu's avatar sunguoshu

优化

parent 31134103
# 公共路径前缀
VITE_PUBLIC_PATH=/
# 端口号
# 开发服务器端口号
VITE_PORT=5176
# 代理地址前缀
# 请求前缀
VITE_BASIC_URL=/api
# 超时
VITE_TIME_OUT=10000
......
# 公共路径前缀
VITE_PUBLIC_PATH=./
# location前缀
VITE_LOCATION_PREFIX=/sso
# 请求前缀
VITE_BASIC_URL=
VITE_BASIC_URL=/api
# 超时
VITE_TIME_OUT=10000
\ No newline at end of file
VITE_TIME_OUT=10000
# 是否开启mock
VITE_MOCK=true
\ No newline at end of file
FROM node:18.16.0-alpine
WORKDIR /var/www/html
COPY package.json .
RUN npm install --registry=https://registry.npmmirror.com
COPY . .
RUN npm run build
FROM keymetrics/pm2:16-buster
WORKDIR /var/node/mockServer
COPY --from=0 /var/www/html/dist/mockServer /var/node/mockServer
RUN npm install --registry=https://registry.npmmirror.com
CMD ["npm", "run", "start"]
\ No newline at end of file
......@@ -7,16 +7,11 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AppContainer: typeof import('./src/layouts/components/AppContainer/components/AppContainer.vue')['default']
AppLoading: typeof import('./src/layouts/components/AppContainer/components/AppLoading.vue')['default']
Index: typeof import('./src/views/system/Affiche.vue')['default']
Lock: typeof import('./src/components/TheLock/components/Lock.vue')['default']
Magnifier: typeof import('./src/components/Magnifier/magnifier.vue')['default']
MagnifierDialog: typeof import('./src/components/Magnifier/magnifierDialog.vue')['default']
Modal: typeof import('./src/components/TheModal/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TheModal: typeof import('./src/components/TheModal/index.vue')['default']
TimeBetween: typeof import('./src/components/TimeBetween/components/TimeBetween.vue')['default']
Upload: typeof import('./src/components/Upload/components/Upload.vue')['default']
}
......
version: '3.7'
services:
nodejs:
build:
context: .
dockerfile: MockDockerfile
container_name: mock
restart: always
ports:
- 3000:3000
networks:
- app-network
nginx:
build:
context: .
dockerfile: Dockerfile
container_name: nginx
restart: always
ports:
- 3001:80
networks:
- app-network
networks:
app-network:
driver: bridge
\ No newline at end of file
......@@ -9,7 +9,6 @@
<body>
<div id="markWaterDiv"></div>
<div id="app"></div>
<script type="module" src="/waterMark.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
......@@ -8,9 +8,19 @@ export default defineMock([
responseCode: '200',
data: {
userId: mockjs.Random.string('number', 10),
userName: mockjs.Random.cname(),
token: mockjs.Random.string('upper', 40)
}
}
},
{
url: '/api/sso/getUserInfo',
body: (request) => {
return {
userId: request.body.userId,
userName: mockjs.Random.cname(),
avatar: mockjs.Random.image('200x200', '#50B347', '#FFF', 'Mock.js'),
email: mockjs.Random.email()
}
}
}
])
import { defineMock } from 'vite-plugin-mock-dev-server'
import mockjs from 'mockjs'
const menu: Menu[] = [
{
......
......@@ -9,13 +9,13 @@ server {
}
# 接口代理示例
# location /api {
# proxy_pass http://xxx.com;
# proxy_set_header Host $host:$server_port;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header Cookie $http_cookie;
# proxy_buffering off;
# proxy_cache off;
# }
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
proxy_buffering off;
proxy_cache off;
}
}
\ No newline at end of file
......@@ -14,7 +14,7 @@ module.exports = defineConfig({
openAPIs: [
{
name: 'index',
document: './openapi/credit.json',
document: './openapi/openapi.json',
},
],
});
\ No newline at end of file
......@@ -86,7 +86,7 @@
},
"/sso/login": {
"post": {
"summary": "未命名接口",
"summary": "登录",
"x-apifox-folder": "",
"x-apifox-status": "developing",
"deprecated": false,
......@@ -160,10 +160,88 @@
},
"x-run-in-apifox": "https://apifox.com/web/project/3045565/apis/api-98004163-run"
}
},
"/sso/getUserInfo": {
"post": {
"summary": "获取用户信息",
"x-apifox-folder": "",
"x-apifox-status": "developing",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string",
"title": "用户ID"
}
},
"x-apifox-orders": [
"userId"
],
"required": [
"userId"
],
"x-apifox-ignore-properties": []
}
}
}
},
"responses": {
"200": {
"description": "成功",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
},
"x-run-in-apifox": "https://apifox.com/web/project/3045565/apis/api-99344559-run"
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"userId": {
"type": "string",
"title": "用户id"
},
"userName": {
"type": "string",
"title": "用户名称"
},
"email": {
"type": "string",
"title": "邮箱"
},
"avatar": {
"type": "string",
"title": "头像"
}
},
"x-apifox-orders": [
"userId",
"userName",
"email",
"avatar"
],
"required": [
"userId"
],
"x-apifox-ignore-properties": [],
"x-apifox-folder": ""
},
"Menu": {
"type": "object",
"properties": {
......
......@@ -30,6 +30,7 @@
"pinia": "^2.1.4",
"pinia-plugin-persist": "^1.0.0",
"vue": "2.7.14",
"vue-i18n": "^8.28.2",
"vue-router": "3.6.5",
"vue2-editor": "^2.10.3",
"vxe-table": "^3.6.13",
......
lockfileVersion: '6.1'
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
......@@ -38,6 +38,9 @@ dependencies:
vue:
specifier: 2.7.14
version: 2.7.14
vue-i18n:
specifier: ^8.28.2
version: 8.28.2(vue@2.7.14)
vue-router:
specifier: 3.6.5
version: 3.6.5(vue@2.7.14)
......@@ -4555,6 +4558,7 @@ packages:
/pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
requiresBuild: true
dev: true
optional: true
......@@ -4643,6 +4647,7 @@ packages:
/prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
requiresBuild: true
dev: true
optional: true
......@@ -4826,6 +4831,7 @@ packages:
/semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
requiresBuild: true
dev: true
optional: true
......@@ -5628,6 +5634,14 @@ packages:
vue: 2.7.14
dev: false
/vue-i18n@8.28.2(vue@2.7.14):
resolution: {integrity: sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA==}
peerDependencies:
vue: ^2
dependencies:
vue: 2.7.14
dev: false
/vue-ref@2.0.0:
resolution: {integrity: sha512-uKNKpFOVeWNqS2mrBZqnpLyXJo5Q+vnkex6JvpENvhXHFNBW/SJTP8vJywLuVT3DpxwXcF9N0dyIiZ4/NpTexQ==}
dev: false
......
......@@ -15,7 +15,23 @@ import axios from '@/utils/remote'
const request = axios.request
/**
* @title 未命名接口
* @title 获取用户信息
* @description
*/
export async function postGetUserInfo(
data?: PostGetUserInfoReqData,
config?: AxiosRequestConfig
): Promise<PostGetUserInfoResData> {
return request({
url: `/sso/getUserInfo`,
method: POST,
data,
...config
})
}
/**
* @title 登录
* @description
*/
export async function postLogin(
......
export default [
{
key: '1',
name: '公告',
list: []
},
{
key: '2',
name: '消息',
list: [
{
id: '000000001',
title: '你收到了 2 份报工提醒',
datetime: '2023-02-28',
type: '1',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
isFinish: true
},
{
id: '000000002',
title: '你收到了 3 份报工提醒',
datetime: '2022-08-09',
type: '1',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
isFinish: false
}
]
},
{
key: '3',
name: '待办',
list: [
{
id: '000000001',
title: '你收到了 14 份新周报',
description: '',
datetime: '2017-08-09',
type: '1',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'
},
{
id: '000000001',
title: '你收到了 14 份新周报',
description: '',
datetime: '2017-08-09',
type: '2',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'
}
]
}
]
......@@ -40,6 +40,7 @@
import { Icon } from '@iconify/vue2'
import { useAppStore, useUserStore, useConfigStore } from '@/store/modules'
import { useTime } from '@/hooks/utils'
const appStore = useAppStore()
const userSystem = useConfigStore()
const userStore = useUserStore()
......
......@@ -3,7 +3,6 @@
<div class="clickBox">
<div class="userClickBox" @click="visible = true">
<img alt="" class="showImg" v-if="isShowImg" :src="`${getBase()}/sso${imageUrl}`" />
<Icon icon="material-symbols:image-search-sharp" v-else />
</div>
</div>
......@@ -40,9 +39,6 @@
action="/sso/system/upload"
@change="onchange"
accept="image/png, image/jpeg"
:headers="{
token: getToken()
}"
>
<div v-if="isShowDialogImg">
<a-icon type="check" />
......@@ -58,9 +54,7 @@
<!-- 推荐320 x 180像素,确保16:9的宽高比。 -->
</div>
</span>
<!-- <h1>123456</h1> -->
</div>
<div class="showBigImg">
<img :src="`${getBase()}/sso${imageDialogUrl}`" alt="" v-show="isShowDialogImg" />
</div>
......@@ -73,8 +67,6 @@
</template>
<script>
import { Icon } from '@iconify/vue2'
import { useUserStore } from '@/store/modules/user'
import { getToken } from '@/utils/token'
export default {
name: 'upload',
......@@ -97,7 +89,6 @@ export default {
imageDialogUrl: '', //图片路径,这个弹框的
isShowImg: false, //这个是表单中是否展示图片
isShowDialogImg: false, //这个是弹框是否展示图片
userStore: useUserStore(),
getToken: getToken
}
},
......@@ -105,7 +96,6 @@ export default {
onchange(value) {
this.imageDialogUrl = value?.file?.response?.path
if (this.imageDialogUrl) {
//选中了首先dialog弹框中的图片显示出来然后弹框点击确定后然后将这个图给表单并渲染出来
this.isShowDialogImg = true
}
},
......
......@@ -5,8 +5,8 @@ Vue.directive('auth', {
inserted(el, binding) {
const userStore = useUserStore()
if (!userStore.authList.includes(String(binding.value))) {
el.parentNode?.removeChild(el)
}
// if (!userStore.authList.includes(String(binding.value))) {
// el.parentNode?.removeChild(el)
// }
}
})
......@@ -16,7 +16,7 @@
</a-badge>
<template #content>
<a-tabs :animated="false">
<template v-for="item in newsList.value">
<template v-for="item in listData">
<a-tabPane :key="item.key">
<template #tab>
{{ item.name }}
......@@ -33,8 +33,8 @@
<Icon icon="codicon:screen-full" v-else />
</span>
<span class="homeUserHead">
<img src="../../../assets/img/header.png" alt="" />
<span class="userInfoBox">{{ userStore?.userInfo?.userName }}</span>
<img :src="userInfo?.avatar" alt="" />
<span class="userInfoBox">{{ userInfo?.userName }}</span>
<div class="userButton">
<span @click="configStore.setLock(true)">
<Icon icon="ion:lock-closed-outline" />锁定屏幕
......@@ -48,78 +48,18 @@
<script lang="ts" setup>
import { Icon } from '@iconify/vue2'
import { prefixCls } from '@/enums/const'
import { useAppStore, useUserStore, useConfigStore, useMenuStore } from '@/store/modules'
import { useRouter } from 'vue-router/composables'
import { computed, ref } from 'vue'
import { useAppStore, useUserStore, useConfigStore } from '@/store/modules'
import NoticeList from './NoticeList.vue'
import { useSystemNoticeStore } from '@/store/modules'
import data from '@/assets/json/notice'
const appStore = useAppStore()
const menuStore = useMenuStore()
const userStore = useUserStore()
const configStore = useConfigStore()
const { userInfo } = storeToRefs(useUserStore())
const router = useRouter()
const visible = ref(false)
const systemNoticeStore = useSystemNoticeStore()
let information = ref(6)
const listData = ref([
{
key: '1',
name: '公告',
list: []
},
{
key: '2',
name: '消息',
list: [
{
id: '000000001',
title: '你收到了 2 份报工提醒',
datetime: '2023-02-28',
type: '1',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
isFinish: true
},
{
id: '000000002',
title: '你收到了 3 份报工提醒',
datetime: '2022-08-09',
type: '1',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
isFinish: false
}
]
},
{
key: '3',
name: '待办',
list: [
{
id: '000000001',
title: '你收到了 14 份新周报',
description: '',
datetime: '2017-08-09',
type: '1',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'
},
{
id: '000000001',
title: '你收到了 14 份新周报',
description: '',
datetime: '2017-08-09',
type: '2',
userPic: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'
}
]
}
])
let newsList = computed(() => {
listData.value[0].list = systemNoticeStore.systemNoticeList
// 回调函数必须return,结果就是计算的结果
// 如果计算属性依赖的数据发生变化,那么会重新计算
return listData
})
const information = ref(6)
const listData = ref(data)
</script>
<style lang="less" scoped>
.@{prefixCls}-headerBtnList {
......
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
const langs: Record<string, any> = import.meta.glob('./modules/*.json', { eager: true })
const messages = Object.keys(langs).reduce((messages, path) => {
const lang = path.match(/modules\/(.*)\.json/)![1]
messages[lang] = langs[path].default
return messages
}, {} as Record<string, any>)
export default new VueI18n({
locale: 'zh-CN',
messages
})
{
"app": {
"name": "信贷系统"
}
}
\ No newline at end of file
import Vue from 'vue'
import pinia from './store'
import App from './App.vue'
import Antd from 'ant-design-vue'
import './themes/default.less'
import moment from 'moment'
import 'moment/locale/zh-cn'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import { createHead, HeadVuePlugin } from '@vueuse/head'
import 'virtual:uno.css'
import i18n from './locales'
import router from './router'
import pinia from './store'
import App from './App.vue'
import './directives'
import { useAppStore } from '@/store/modules'
import 'virtual:uno.css'
import './themes/default.less'
import { createWaterMark } from './utils/waterMark'
import VXETable from 'vxe-table'
import 'vxe-table/lib/style.css'
import { useAppStore } from '@/store/modules'
import TimeDate from '@/components/TimeDate'
import Step from '@/components/Step'
import magnifier from '@/components/Magnifier/magnifier.vue'
import { post } from '@/utils/remote/remote'
import api from '@/utils/remote'
import dayjs from 'dayjs'
import { VITE_APP_NAME } from '@/enums/env'
dayjs.locale('zh-cn')
const isLogin = location.hash.startsWith('#/login')
......@@ -50,11 +53,13 @@ Vue.config.productionTip = false
Vue.config.devtools = false
new Vue({
i18n,
pinia,
router,
render: (h) => h(App)
})
.$mount('#app')
.$nextTick(() => {
createWaterMark(VITE_APP_NAME)
!isLogin && useAppStore()
})
import { router } from '@/router'
import { setupLayouts } from 'virtual:generated-layouts'
import generatedRoutes from 'virtual:generated-pages'
import { postQueryMenu } from '@/apis'
import { useMenuStore, useConfigStore } from '@/store/modules'
import { postGetUserInfo, postQueryMenu } from '@/apis'
import { useConfigStore, useMenuStore, useUserStore } from '@/store/modules'
import { exclude } from '@/utils'
export const useAppStore = defineStore('app', () => {
const route = location.hash.slice(1)
......@@ -11,13 +12,13 @@ export const useAppStore = defineStore('app', () => {
const menuStore = useMenuStore()
const configStore = useConfigStore()
const userStore = useUserStore()
if (!userId) {
location.hash = '#/login'
}
let authRoutes: string[] = [] // 权限路由
let currentMenu: any // 当前路由菜单
// 递归转换菜单
function transformToMenu(item: Menu[]): any[] {
......@@ -31,11 +32,7 @@ export const useAppStore = defineStore('app', () => {
}
if (i.type === 'menu') {
authRoutes.push(i.path!)
const menu = { ...obj, path: i.path }
if (i.path === route) {
currentMenu = menu
}
return menu
return { ...obj, path: i.path }
}
return {
...obj,
......@@ -44,10 +41,35 @@ export const useAppStore = defineStore('app', () => {
})
}
// 递归查找菜单
function findMenu(menu: MenuRouteConfig[], openedMenu: MenuRouteConfig[] = []) {
let currentMenu: any
for (const item of menu) {
if (item.path === route) {
currentMenu = item
break
}
if (item.children) {
openedMenu.push(item)
currentMenu = findMenu(item.children, openedMenu)
if (currentMenu) {
break
} else {
openedMenu.pop()
}
}
}
return currentMenu
}
function setUser(data: User) {
userStore.setUserId(userId!)
userStore.setUserInfo(data)
}
function setMenu(data: any[]) {
const menu = transformToMenu(data)
menuStore.setMenu(menu)
console.log(menu)
}
function setRoutes() {
......@@ -56,11 +78,21 @@ export const useAppStore = defineStore('app', () => {
}
function setState() {
let openedMenu: MenuRouteConfig[] = []
let currentMenu = findMenu(menuStore.menu, openedMenu)
if (currentMenu) {
menuStore.setSelectedMenu(currentMenu.name)
menuStore.setOpenedMenu(openedMenu.map((i) => i.name))
menuStore.addVisitedMenu(currentMenu)
}
}
postGetUserInfo({ userId: userId! }).then((r) => {
if (r) {
setUser(r)
}
})
postQueryMenu().then((r) => {
if (r.data) {
setMenu(r.data)
......
......@@ -2,4 +2,3 @@ export * from './app'
export * from './user'
export * from './config'
export * from './menu'
export * from './systemNotice'
interface noticeList {
systemNoticeList: object[]
}
export const useSystemNoticeStore = defineStore('systemNotice', {
state: (): noticeList => ({
systemNoticeList: []
}),
actions: {
setSystemNoticeList(list: object[]) {
this.systemNoticeList = list
}
}
})
interface UserState {
userId: string | null
exp: number | null
userInfo: any
authList: string[]
}
export const useUserStore = defineStore('user', () => {
const userId = ref<string>()
const userInfo = ref<User>()
export const useUserStore = defineStore('user', {
state: (): UserState => ({
userId: null,
exp: null,
userInfo: null,
authList: []
}),
getters: {
isLogin(state) {},
isAdmin(state) {
return state.userInfo?.roleData.some((item: any) => item.roleKey.includes('admin'))
}
},
actions: {
setUserId(userId: string | null) {
this.userId = userId
},
setExp(exp: number | null) {
this.exp = exp
},
setUserInfo(userInfo: any) {
this.userInfo = userInfo
},
setAuthList(authList: string[]) {
this.authList = authList
},
clear() {
this.userId = null
this.exp = null
this.userInfo = null
this.authList = []
}
},
persist: {
enabled: true,
strategies: [
{
storage: window.localStorage,
paths: ['userId', 'exp', 'userInfo']
}
]
if (!userId) {
location.hash = '#/login'
}
function setUserId(id: string) {
userId.value = id
}
function setUserInfo(data: any) {
userInfo.value = data
}
return {
userId,
userInfo,
setUserId,
setUserInfo
}
})
export * from './object'
export * from './tree'
export const pick = (obj: any, keys: string[]) => {
return keys.reduce((a, c) => {
if (obj[c]) {
a[c] = obj[c]
}
return a
}, {} as any)
}
export const exclude = (obj: any, keys: string[]) => {
return Object.keys(obj).reduce((a, c) => {
if (!keys.includes(c)) {
a[c] = obj[c]
}
return a
}, {} as any)
}
;(function createWaterMark(
// @ts-nocheck
export function createWaterMark(
markName,
fontColor = 'rgba(210,210,230,0.5)',
fontSize = '30',
......@@ -31,4 +32,4 @@
ctx.fillText(markName, 0, 300)
let imageUrl = canvas.toDataURL('image/png')
markDiv.style.backgroundImage = 'url(' + imageUrl + ')'
})(process.env.VITE_APP_NAME)
}
<template>
<div class="w-100 h-25 bg-violet">
<h1 class="text-3xl font-bold underline">Hello world!</h1>
<h1 class="text-3xl font-bold underline">
{{ $t('app.name') }}
</h1>
</div>
</template>
......
......@@ -36,6 +36,32 @@ declare global {
type: 'dir' | 'menu'
}
export type User = {
/**
* @title 头像
*/
avatar?: string
/**
* @title 邮箱
*/
email?: string
/**
* @title 用户id
*/
userId: string
/**
* @title 用户名称
*/
userName?: string
}
export type PostGetUserInfoReqData = {
/**
* @title 用户ID
*/
userId: string
}
export type PostGetUserInfoResData = User
export type PostLoginReqData = {
/**
* @title 密码
......
import type { RouteConfigSingleView } from 'vue-router/types/router'
declare global {
export interface MenuMeta {
title?: string
......
......@@ -50,7 +50,7 @@ export default defineConfig(({ mode }) => {
host: true,
port: Number(VITE_PORT),
https: VITE_HTTPS === 'true',
open: true,
open: false,
proxy: {
'/api': {
target: 'http://www.mock.com/', // mock
......@@ -109,7 +109,12 @@ export default defineConfig(({ mode }) => {
gzipSize: true,
brotliSize: true
}),
isMock && mockDevServerPlugin({})
isMock &&
mockDevServerPlugin({
build: {
serverPort: 3000
}
})
]
}
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment