v2 refacting
This commit is contained in:
+7
-4
@@ -6,12 +6,15 @@
|
||||
VITE_MODE_NAME=development
|
||||
|
||||
# 资源 CDN 前缀
|
||||
VITE_RES_URL=http://localhost:3000/
|
||||
VITE_RES_URL=http://localhost:5173/
|
||||
|
||||
# 后端 API 基础地址
|
||||
# dev: http://localhost:8080/auth
|
||||
# ⚠️ 本地必须写前端自己(http://localhost:5173),由 Vite proxy 转发
|
||||
# 写 http://localhost:8080/auth 会让浏览器绕过 proxy 直连 8080
|
||||
# dev: http://localhost:5173
|
||||
# dev (K8s): https://dev.violin-home.cn
|
||||
# prod: https://www.violin-home.cn
|
||||
VITE_APP_URL=http://localhost:8080/auth
|
||||
VITE_APP_URL=http://localhost:5173
|
||||
|
||||
# 应用元信息
|
||||
VITE_APP_TITLE=violin-home
|
||||
@@ -35,7 +38,7 @@ VITE_OIDC_ISSUER=https://auth.violin-work.online
|
||||
VITE_OIDC_CLIENT_ID=
|
||||
|
||||
# 回调 URI(必须与 Authentik 后台配置的完全一致)
|
||||
# dev: http://localhost:3000/auth/callback
|
||||
# dev: http://localhost:5173/auth/callback
|
||||
# prod: https://www.violin-home.cn/auth/callback
|
||||
VITE_OIDC_REDIRECT_URI=
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -14,7 +14,7 @@
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.8.4",
|
||||
"element-plus": "^2.14.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md-editor-v3": "^4.20.1",
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.8.4",
|
||||
"element-plus": "^2.14.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md-editor-v3": "^4.20.1",
|
||||
|
||||
+35
-11
@@ -1,26 +1,50 @@
|
||||
import { service } from '../utils/request'
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json;charsetset=UTF-8'
|
||||
}
|
||||
|
||||
const put_event = (data: Object) => {
|
||||
/**
|
||||
* 日历事件 API
|
||||
* 对应后端 CalendarController: /api/v1/events
|
||||
*/
|
||||
const post_event = (data: {
|
||||
eventDate: string
|
||||
title: string
|
||||
eventInfo?: string
|
||||
}) => {
|
||||
return service({
|
||||
url: '/calendar/api/v1/event',
|
||||
method: 'PUT',
|
||||
data: data
|
||||
url: '/calendar/api/v1/events',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
const get_event = () => {
|
||||
return service({
|
||||
url: '/calendar/api/v1/event',
|
||||
method: 'GET',
|
||||
headers: headers
|
||||
url: '/calendar/api/v1/events',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
const put_event = (id: number, data: {
|
||||
eventDate: string
|
||||
title: string
|
||||
eventInfo?: string
|
||||
}) => {
|
||||
return service({
|
||||
url: `/calendar/api/v1/events/${id}`,
|
||||
method: 'PUT',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
const delete_event = (id: number) => {
|
||||
return service({
|
||||
url: `/calendar/api/v1/events/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
get_event,
|
||||
post_event,
|
||||
put_event,
|
||||
delete_event,
|
||||
}
|
||||
@@ -12,12 +12,14 @@
|
||||
<MainApp></MainApp>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<span>
|
||||
管祥玮的个人网站 ©Copyright 2022-2022
|
||||
</span>
|
||||
<span @click="openSiteQuery">辽ICP备2022003637号-2</span>
|
||||
<div>
|
||||
<el-avatar @click="openGithub" src="https://github.githubassets.com/favicons/favicon.svg" />
|
||||
<div class="footer-left">
|
||||
<span>{{ SITE_NAME }} {{ COPYRIGHT_TEXT }}</span>
|
||||
<span class="separator">|</span>
|
||||
<span class="icp-link" @click="openSiteQuery">{{ ICP_NUMBER }}</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<el-avatar :size="24" class="github-icon" @click="openGithub"
|
||||
src="https://github.githubassets.com/favicons/favicon.svg" />
|
||||
</div>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
@@ -30,6 +32,7 @@ import MainApp from '../layout/MainApp.vue'
|
||||
import NavBar from "../layout/NavBar.vue"
|
||||
import SideBar from "../layout/SideBar.vue"
|
||||
import { settingsStore } from "@/store/modules/settings"
|
||||
import { SITE_NAME, COPYRIGHT_TEXT, ICP_NUMBER, ICP_QUERY_URL } from "@/const/site"
|
||||
|
||||
// -- IMPORT --
|
||||
const useSettingsStore = settingsStore()
|
||||
@@ -62,8 +65,7 @@ const openGithub = () => {
|
||||
}
|
||||
|
||||
const openSiteQuery = () => {
|
||||
let href = 'https://beian.miit.gov.cn/#/Integrated/index'
|
||||
window.open(href, '_blank')
|
||||
window.open(ICP_QUERY_URL, '_blank')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,9 +83,12 @@ iniPage()
|
||||
}
|
||||
|
||||
.el-main {
|
||||
/* 让 main 区占满 header 与 footer 之间的剩余空间,
|
||||
内容多了 main 内部滚动,footer 永远钉在 layout 底部 */
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0; /* flex item 默认 min-height: auto 会阻止收缩 */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
@@ -93,13 +98,50 @@ iniPage()
|
||||
}
|
||||
|
||||
.el-footer {
|
||||
/* flex: 0 0 auto 让 footer 保持自身高度(不撑开、不收缩) */
|
||||
flex: 0 0 auto;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 24px;
|
||||
border-top: 1px solid rgba(151, 151, 151, 0.2);
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.icp-link {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.icp-link:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.github-icon {
|
||||
/* 跟 footer 文字 baseline 对齐 */
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main_container {
|
||||
/* height: 100%; */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.homeWrap {
|
||||
|
||||
@@ -18,10 +18,15 @@
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="footer">
|
||||
<span>
|
||||
管祥玮的个人网站 ©Copyright 2022-2022
|
||||
</span>
|
||||
<span @click="openSiteQuery">辽ICP备2022003637号-2</span>
|
||||
<div class="footer-left">
|
||||
<span>{{ SITE_NAME }} {{ COPYRIGHT_TEXT }}</span>
|
||||
<span class="separator">|</span>
|
||||
<span class="icp-link" @click="openSiteQuery">{{ ICP_NUMBER }}</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<el-avatar :size="24" class="github-icon" @click="openGithub"
|
||||
src="https://github.githubassets.com/favicons/favicon.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -29,6 +34,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { SITE_NAME, COPYRIGHT_TEXT, ICP_NUMBER, ICP_QUERY_URL } from '@/const/site'
|
||||
|
||||
const handleOidcLogin = () => {
|
||||
const issuer = import.meta.env.VITE_OIDC_ISSUER
|
||||
@@ -49,8 +55,11 @@ const handleOidcLogin = () => {
|
||||
}
|
||||
|
||||
const openSiteQuery = () => {
|
||||
let href = 'https://beian.miit.gov.cn/#/Integrated/index'
|
||||
window.open(href, '_blank')
|
||||
window.open(ICP_QUERY_URL, '_blank')
|
||||
}
|
||||
|
||||
const openGithub = () => {
|
||||
window.open('https://github.com/simple321vip', '_blank')
|
||||
}
|
||||
|
||||
const style = reactive({} as any)
|
||||
@@ -67,6 +76,9 @@ style.width = window.innerWidth * 0.7 + 'px'
|
||||
}
|
||||
|
||||
.ttt {
|
||||
/* flex column + min-height 100vh:footer 永远在视口底部,
|
||||
内容少时 footer 贴底,内容多时 footer 在内容之后但不漂 */
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -79,4 +91,49 @@ style.width = window.innerWidth * 0.7 + 'px'
|
||||
background: url('../../assets/have_a_nice_day.jpeg') center top no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
/* 中间内容区撑开剩余空间 */
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 24px;
|
||||
border-top: 1px solid rgba(151, 151, 151, 0.2);
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.icp-link {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.icp-link:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.github-icon {
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 站点元信息常量。
|
||||
*
|
||||
* <p>⚠️ ICP 备案号是公网安部要求强制展示的,<b>不能修改</b>。文案变更需重新备案。</p>
|
||||
*/
|
||||
|
||||
/** 产品名(footer / 浏览器 title / 侧边栏使用) */
|
||||
export const SITE_NAME = 'Violin Home'
|
||||
|
||||
/** 版权年份区间(自动取当前年) */
|
||||
export const COPYRIGHT_YEARS = `2022-${new Date().getFullYear()}`
|
||||
|
||||
/** 完整版权字符串(footer 用) */
|
||||
export const COPYRIGHT_TEXT = `©Copyright ${COPYRIGHT_YEARS}`
|
||||
|
||||
/** ICP 备案号(⚠️ 公网安部要求原样显示,不可修改) */
|
||||
export const ICP_NUMBER = '辽ICP备2022003637号-2'
|
||||
|
||||
/** ICP 备案查询 URL(公网安部要求链接到工信部查询页) */
|
||||
export const ICP_QUERY_URL = 'https://beian.miit.gov.cn/#/Integrated/index'
|
||||
+8
-4
@@ -19,11 +19,15 @@ type DataTimeline = {
|
||||
message: string,
|
||||
}
|
||||
|
||||
// 与后端 CalendarEventResponse 对齐(camelCase)
|
||||
type Event = {
|
||||
reminder_date: string,
|
||||
title: string,
|
||||
info: string,
|
||||
type: string[],
|
||||
id?: number
|
||||
customerId?: string
|
||||
eventDate: string
|
||||
title: string
|
||||
eventInfo?: string
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
interface Wiki {
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { createRouter, createMemoryHistory, RouteRecordRaw } from 'vue-router'
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const login = () => import('@/components/login/login.vue')
|
||||
const home = () => import('@/view/dashboard/index.vue')
|
||||
@@ -134,7 +134,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
|
||||
+41
-5
@@ -1,8 +1,8 @@
|
||||
import axios from 'axios'
|
||||
import router from '../router'
|
||||
import { getToken, getCustomer, resetToken } from '../utils/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// violin-wiki
|
||||
const service = axios.create(
|
||||
{
|
||||
baseURL: import.meta.env.VITE_APP_URL as string,
|
||||
@@ -13,8 +13,12 @@ const service = axios.create(
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
if (typeof getToken() === 'string') {
|
||||
(config as any).headers['Authorization'] = 'Bearer:' + getToken()
|
||||
// ⚠️ Bearer 后必须有空格,否则后端 JWT 解析器会识别失败 → 401
|
||||
(config as any).headers['Authorization'] = 'Bearer ' + getToken()
|
||||
}
|
||||
// 当前域名 → 后端按域名解析 customer(local 自动归 dev,prod 必须 admin 预先注册)
|
||||
;(config as any).headers['X-Customer-Domain'] = window.location.hostname
|
||||
|
||||
const customer = getCustomer()
|
||||
if (typeof customer === 'string') {
|
||||
(config as any).headers['customerId'] = JSON.parse(customer).customerId
|
||||
@@ -26,16 +30,48 @@ service.interceptors.request.use(
|
||||
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
const data = response.data
|
||||
// 后端 ApiResponse / ErrorResponse 结构:{ code, message, ... }
|
||||
// 业务 code 非 0 / AUTH_401 等都按错误处理
|
||||
if (data && typeof data === 'object' && 'code' in data) {
|
||||
if (data.code === 0 || data.code === '0' || data.code === 200) {
|
||||
return response
|
||||
}
|
||||
// 401 业务码:跳登录
|
||||
if (data.code === 'AUTH_401' || data.code === 'AUTH_CONTEXT_MISSING') {
|
||||
ElMessage.warning('登录已过期,请重新登录')
|
||||
resetToken()
|
||||
router.replace('/login').catch(() => {})
|
||||
return Promise.reject(new Error(data.message || '未登录'))
|
||||
}
|
||||
// 其他业务错误:弹错
|
||||
ElMessage.error(data.message || `请求失败 (${data.code})`)
|
||||
return Promise.reject(new Error(data.message || '请求失败'))
|
||||
}
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
if (error && error.response) {
|
||||
if (error.response.status == 401) {
|
||||
const status = error.response.status
|
||||
const body = error.response.data
|
||||
if (status === 401) {
|
||||
// HTTP 401:登录失效
|
||||
const msg = (body && body.message) || '登录已过期,请重新登录'
|
||||
ElMessage.warning(msg)
|
||||
resetToken()
|
||||
router.resolve({ path: '/' });
|
||||
return
|
||||
router.replace('/login').catch(() => {})
|
||||
} else if (status >= 500) {
|
||||
ElMessage.error((body && body.message) || '服务器内部错误')
|
||||
} else if (status >= 400) {
|
||||
ElMessage.error((body && body.message) || `请求错误 (${status})`)
|
||||
} else {
|
||||
ElMessage.error('网络异常')
|
||||
}
|
||||
} else {
|
||||
// 无 response:网络层 / CORS / 超时
|
||||
ElMessage.error(error.message || '网络异常')
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -34,10 +34,13 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
setToken(res.data.token)
|
||||
// 后端 ApiResponse 结构:{ code, message, data: { token, sub, username, ... } }
|
||||
// 业务字段在 data.data 下
|
||||
const payload = (res.data && res.data.data) || res.data
|
||||
setToken(payload.token)
|
||||
setCustomer({
|
||||
customerId: res.data.sub,
|
||||
account: res.data.username
|
||||
customerId: payload.customerId || payload.sub,
|
||||
account: payload.username
|
||||
})
|
||||
sessionStorage.removeItem('oidc_state')
|
||||
router.push('/')
|
||||
|
||||
+51
-33
@@ -1,44 +1,47 @@
|
||||
<template>
|
||||
<el-calendar v-model="value" v-if="isComponentActive" v-loading="isComponentLoading"
|
||||
element-loading-text="主人,别着急我在努力加载中^_^">
|
||||
<template #dateCell="{ data }" lo>
|
||||
<div class="date-cell-holiday" v-if="Object.keys(holidays).includes(toLocalDate(data.date))">
|
||||
<span style="color: red;" @click="onclick(data.date)"> {{ data.date.getDate() }}</span><br>
|
||||
<span>{{ holidays[toLocalDate(data.date)] }}</span>
|
||||
<template #date-cell="{ data }">
|
||||
<div class="date-cell-holiday"
|
||||
v-if="Object.keys(holidays).includes(data.day)"
|
||||
@click="onclick(data.date)"
|
||||
style="cursor: pointer;">
|
||||
<span style="color: red;">{{ data.day.split('-')[2] }}</span><br>
|
||||
<span>{{ holidays[data.day] }}</span>
|
||||
</div>
|
||||
<div class="date-cell" v-else>
|
||||
<span style="color: red;" v-if="data.date.getDay() == 0 || data.date.getDay() == 6" @click="onclick(data.date)">
|
||||
{{
|
||||
data.date.getDate()
|
||||
}}</span>
|
||||
<span v-else @click="onclick(data.date)"> {{ data.date.getDate() }}</span>
|
||||
<div class="date-cell" v-else @click="onclick(data.date)" style="cursor: pointer;">
|
||||
<span style="color: red;" v-if="data.date.getDay() == 0 || data.date.getDay() == 6">
|
||||
{{ data.day.split('-')[2] }}
|
||||
</span>
|
||||
<span v-else> {{ data.day.split('-')[2] }}</span>
|
||||
</div>
|
||||
<div class="date-cell-event" v-if="Object.keys(eventDataObject).includes(toLocalDate(data.date))">
|
||||
<span>{{ eventDataObject[toLocalDate(data.date)] }}</span>
|
||||
<div class="date-cell-event" v-if="Object.keys(eventDataObject).includes(data.day)">
|
||||
<span>{{ eventDataObject[data.day] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
|
||||
<span>待办事项</span>
|
||||
<el-table :data="eventDataList">
|
||||
<el-table-column property="reminder_date" label="Date" width="150" />
|
||||
<el-table-column property="eventDate" label="Date" width="150" />
|
||||
<el-table-column property="title" label="title" width="200" />
|
||||
<el-table-column property="reminder_info" label="eventInfo" />
|
||||
<el-table-column property="eventInfo" label="eventInfo" />
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" title="待办事项">
|
||||
<el-form :model="form">
|
||||
<el-form-item label="日期" :label-width="formLabelWidth">
|
||||
<span>{{ form.reminder_date }}</span>
|
||||
<span>{{ form.eventDate }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="title" :label-width="formLabelWidth">
|
||||
<el-input v-model="form.title" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="eventInfo" :label-width="formLabelWidth">
|
||||
<el-input v-model="form.reminder_info" autocomplete="off" type="textarea" />
|
||||
<el-input v-model="form.eventInfo" autocomplete="off" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="alert" :label-width="formLabelWidth">
|
||||
<el-checkbox-group v-model="checkedalerts" :min="0" :max="2">
|
||||
<el-checkbox v-for="city in alerts" :key="city" :label="city">{{
|
||||
<el-checkbox v-for="city in alerts" :key="city" :value="city">{{
|
||||
city
|
||||
}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
@@ -59,24 +62,24 @@
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { get_holiday } from '@/service/calendar'
|
||||
import { toLocalDate } from '@/utils/date'
|
||||
import { get_event, put_event } from "@/api/calendar"
|
||||
import { Event } from "@/entity/index"
|
||||
import { settingsStore } from "@/store/modules/settings";
|
||||
import { get_event, post_event, put_event, delete_event } from '@/api/calendar'
|
||||
import { Event } from '@/entity/index'
|
||||
import { settingsStore } from '@/store/modules/settings'
|
||||
|
||||
|
||||
// -- IMPORT --
|
||||
const useSettingsStore = settingsStore()
|
||||
|
||||
// -- REACTIVE OBJECT --
|
||||
const holidays = reactive<any>({})
|
||||
const holidays = reactive<Record<string, string>>({})
|
||||
|
||||
const eventDataList = reactive<Event[]>([])
|
||||
const eventDataObject = reactive<any>({})
|
||||
const eventDataObject = reactive<Record<string, string>>({})
|
||||
const form = reactive({
|
||||
reminder_date: '',
|
||||
// 与后端 CalendarEventRequest 对齐(camelCase)
|
||||
eventDate: '',
|
||||
title: '',
|
||||
reminder_info: '',
|
||||
|
||||
eventInfo: '',
|
||||
})
|
||||
// -- REF OBJECT --
|
||||
const value = ref(new Date())
|
||||
@@ -90,13 +93,15 @@ const alerts = ['email', 'messageBox', '手机短信', '微信通知']
|
||||
|
||||
// -- EVENT DEFINITION
|
||||
const onclick = (date: Date) => {
|
||||
form.reminder_date = toLocalDate(date)
|
||||
form.eventDate = toLocalDate(date)
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const confirm = async () => {
|
||||
await put_event(form).then(() => {
|
||||
|
||||
await post_event({
|
||||
eventDate: form.eventDate,
|
||||
title: form.title,
|
||||
eventInfo: form.eventInfo,
|
||||
})
|
||||
dialogFormVisible.value = false
|
||||
handleCommand('refresh')
|
||||
@@ -121,19 +126,32 @@ const handleCommand = (command: string) => {
|
||||
* AUTO INVOKE FUNCTION
|
||||
*/
|
||||
onMounted(async () => {
|
||||
// 先显示日历(避免数据加载失败阻塞日历渲染)
|
||||
isComponentActive.value = true
|
||||
|
||||
await get_holiday().then((resp) => {
|
||||
// holidays-jp.github.io 直接返回 JSON 对象,不是 ApiResponse 结构
|
||||
Object.keys(resp.data).forEach(element => {
|
||||
holidays[element] = resp.data[element]
|
||||
})
|
||||
}).catch(() => {
|
||||
// 节假日数据失败不影响日历显示
|
||||
})
|
||||
|
||||
await get_event().then((response) => {
|
||||
// 后端 ApiResponse 结构:{code, message, data: [...]}
|
||||
const list = response.data?.data ?? response.data
|
||||
eventDataList.length = 0
|
||||
response.data.forEach((record: Event) => {
|
||||
eventDataList.push(record)
|
||||
eventDataObject[record.reminder_date] = record.title
|
||||
})
|
||||
if (Array.isArray(list)) {
|
||||
list.forEach((record: Event) => {
|
||||
eventDataList.push(record)
|
||||
eventDataObject[record.eventDate] = record.title
|
||||
})
|
||||
}
|
||||
}).catch(() => {
|
||||
// 业务异常已经被 axios 拦截器 toast 了
|
||||
})
|
||||
isComponentActive.value = true
|
||||
|
||||
isComponentLoading.value = false
|
||||
})
|
||||
|
||||
|
||||
+67
-4
@@ -7,16 +7,19 @@ import { fileURLToPath, URL } from 'url'
|
||||
// https://vitejs.dev/guide/env-and-mode.html#env-files
|
||||
export default defineConfig(({ mode, command }) => {
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
// mock 开关:仅 serve(dev)模式 + .env.local 里 VITE_USE_MOCK=true
|
||||
// 关掉时直接不注册 viteMockServe 插件(避免 mock routes 被注册)
|
||||
const useMock = command === 'serve' && env.VITE_USE_MOCK === 'true'
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
// gzip 压缩
|
||||
viteCompression(),
|
||||
// 本地 mock 服务(仅 dev 启用,prod 不引入)
|
||||
viteMockServe({
|
||||
// 本地 mock 服务(由 VITE_USE_MOCK 控制;prod / 关 mock 时不引入)
|
||||
...(useMock ? [viteMockServe({
|
||||
mockPath: 'mock',
|
||||
localEnabled: command === 'serve',
|
||||
}),
|
||||
localEnabled: true,
|
||||
})] : []),
|
||||
],
|
||||
base: env.VITE_RES_URL,
|
||||
resolve: {
|
||||
@@ -31,6 +34,66 @@ export default defineConfig(({ mode, command }) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// dev server proxy —— 本地多后端联调
|
||||
// ============================================================
|
||||
// ⚠️ 关键:proxy key 必须用 /api/ 后缀,避免拦截 SPA 路由
|
||||
// 例如 /bookmark/api/v1/... → 8086(后端 API)
|
||||
// 但 /bookmark → 保留在前端(SPA 路由,由 history fallback 处理)
|
||||
//
|
||||
// 调试建议:浏览器开 Network,看 Server 响应头确认走了哪个后端
|
||||
//
|
||||
// 端口对齐:
|
||||
// violin-auth: 8080 (context-path: /auth)
|
||||
// violin-calendar: 8085 (context-path: /calendar)
|
||||
// violin-bookmark: 8086 (context-path: /bookmark, 待你确认)
|
||||
// violin-onenote: 8087 (context-path: /onenote, 待你确认)
|
||||
// ... 新增服务时在这里加一行
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
// auth: 业务 API 在 /api/v1/auth/...,没有 /auth/api 前缀
|
||||
// 用 rewrite 把 /api/v1/auth/* 转成 /auth/api/v1/auth/*
|
||||
// 后端 context-path 是 /auth,所以请求路径需要带 /auth 前缀
|
||||
'^/api/v1/auth/.*': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/v1\/auth/, '/auth/api/v1/auth'),
|
||||
},
|
||||
// 其他服务:path 自带服务名前缀(/calendar/api/...)
|
||||
'^/calendar/api/.*': {
|
||||
target: 'http://localhost:8085',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/bookmark/api/.*': {
|
||||
target: 'http://localhost:8086',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/onenote/api/.*': {
|
||||
target: 'http://localhost:8087',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/trader/api/.*': {
|
||||
target: 'http://localhost:8088',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/traderCalender/api/.*': {
|
||||
target: 'http://localhost:8088',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/cloud/api/.*': {
|
||||
target: 'http://localhost:8089',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/wiki/api/.*': {
|
||||
target: 'http://localhost:8090',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
build: {
|
||||
// 拆 chunk,减少首屏加载
|
||||
rollupOptions: {
|
||||
|
||||
@@ -1114,7 +1114,7 @@ ee-first@1.1.1:
|
||||
resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
element-plus@^2.8.4:
|
||||
element-plus@^2.14.1:
|
||||
version "2.14.2"
|
||||
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.14.2.tgz"
|
||||
integrity sha512-eNH9uP3wQoNqieEIHXiNvIVv+zO5sZDU0CAZq5b0zqSN06DD0/V9xIq1R/qm3rw5k3nBTM1JvpxhCfRbaFLzDQ==
|
||||
|
||||
Reference in New Issue
Block a user