mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
chore: 更新依赖, 支持日程表单日多日程
This commit is contained in:
@@ -1,28 +1,75 @@
|
|||||||
import oxlint from 'eslint-plugin-oxlint';
|
import antfu from '@antfu/eslint-config'
|
||||||
import vue from 'eslint-plugin-vue';
|
|
||||||
import ts from 'typescript-eslint';
|
|
||||||
|
|
||||||
// `VueVine()` 返回一个 ESLint flat config
|
|
||||||
import VueVine from '@vue-vine/eslint-config'
|
import VueVine from '@vue-vine/eslint-config'
|
||||||
|
|
||||||
export default [
|
export default antfu(
|
||||||
{
|
{
|
||||||
languageOptions: {
|
// 项目类型: app (默认) 或 lib
|
||||||
ecmaVersion: 'latest',
|
type: 'app',
|
||||||
|
|
||||||
|
// 启用 TypeScript 支持 (自动检测)
|
||||||
|
typescript: {
|
||||||
|
tsconfigPath: 'tsconfig.json',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 启用 Vue 支持 (自动检测)
|
||||||
|
vue: true,
|
||||||
|
|
||||||
|
// 启用格式化规则
|
||||||
|
stylistic: {
|
||||||
|
indent: 2,
|
||||||
|
quotes: 'single',
|
||||||
|
semi: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 禁用某些文件类型的支持
|
||||||
|
jsonc: true,
|
||||||
|
yaml: true,
|
||||||
|
markdown: true,
|
||||||
|
|
||||||
|
// 忽略的文件
|
||||||
|
ignores: [
|
||||||
|
'**/node_modules',
|
||||||
|
'**/dist',
|
||||||
|
'**/output',
|
||||||
|
'**/.vitepress/cache',
|
||||||
|
'**/.nuxt',
|
||||||
|
'**/.next',
|
||||||
|
'**/.vercel',
|
||||||
|
'**/.changeset',
|
||||||
|
'**/.idea',
|
||||||
|
'**/.cache',
|
||||||
|
'**/.output',
|
||||||
|
'**/.vite-inspect',
|
||||||
|
'**/CHANGELOG*.md',
|
||||||
|
'**/*.min.*',
|
||||||
|
'**/LICENSE*',
|
||||||
|
'**/__snapshots__',
|
||||||
|
'**/auto-import?(s).d.ts',
|
||||||
|
'**/components.d.ts',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
...vue.configs['flat/recommended'],
|
|
||||||
{
|
{
|
||||||
// files: ['*.vue', '**/*.vue'],
|
// 自定义规则
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
parser: ts.parser,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
"vue/no-mutating-props": "off",
|
// Vue 相关规则
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'vue/no-mutating-props': 'off',
|
||||||
|
'vue/no-v-html': 'off',
|
||||||
|
'vue/require-default-prop': 'off',
|
||||||
|
|
||||||
|
// TypeScript 相关规则
|
||||||
|
'ts/no-explicit-any': 'off',
|
||||||
|
'ts/ban-ts-comment': 'off',
|
||||||
|
|
||||||
|
// 通用规则
|
||||||
|
'no-console': 'off',
|
||||||
|
'unused-imports/no-unused-vars': 'warn',
|
||||||
|
|
||||||
|
// 关闭一些过于严格的规则
|
||||||
|
'antfu/if-newline': 'off',
|
||||||
|
'style/brace-style': ['error', '1tbs'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 集成 VueVine 配置
|
||||||
...VueVine(),
|
...VueVine(),
|
||||||
...oxlint.configs['flat/recommended'], // oxlint should be the last one
|
)
|
||||||
]
|
|
||||||
|
|||||||
82
package.json
82
package.json
@@ -13,84 +13,82 @@
|
|||||||
"@guolao/vue-monaco-editor": "^1.5.5",
|
"@guolao/vue-monaco-editor": "^1.5.5",
|
||||||
"@hyperdx/browser": "^0.21.2",
|
"@hyperdx/browser": "^0.21.2",
|
||||||
"@hyperdx/cli": "^0.1.0",
|
"@hyperdx/cli": "^0.1.0",
|
||||||
"@microsoft/signalr": "^8.0.7",
|
"@microsoft/signalr": "^9.0.6",
|
||||||
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
|
"@microsoft/signalr-protocol-msgpack": "^9.0.6",
|
||||||
"@mixer/postmessage-rpc": "^1.1.4",
|
"@mixer/postmessage-rpc": "^1.1.4",
|
||||||
"@oneidentity/zstd-js": "^1.0.3",
|
"@oneidentity/zstd-js": "^1.0.3",
|
||||||
"@tauri-apps/api": "^2.5.0",
|
"@tauri-apps/api": "^2.8.0",
|
||||||
"@tauri-apps/plugin-autostart": "^2.3.0",
|
"@tauri-apps/plugin-autostart": "^2.5.0",
|
||||||
"@tauri-apps/plugin-http": "^2.4.4",
|
"@tauri-apps/plugin-http": "^2.5.2",
|
||||||
"@tauri-apps/plugin-log": "^2.4.0",
|
"@tauri-apps/plugin-log": "^2.7.0",
|
||||||
"@tauri-apps/plugin-notification": "^2.2.2",
|
"@tauri-apps/plugin-notification": "^2.3.1",
|
||||||
"@tauri-apps/plugin-opener": "^2.2.7",
|
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||||
"@tauri-apps/plugin-os": "^2.2.1",
|
"@tauri-apps/plugin-os": "^2.3.1",
|
||||||
"@tauri-apps/plugin-process": "^2.2.1",
|
"@tauri-apps/plugin-process": "^2.3.0",
|
||||||
"@tauri-apps/plugin-store": "^2.2.0",
|
"@tauri-apps/plugin-store": "^2.4.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
"@tauri-apps/plugin-updater": "^2.9.0",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/md5": "^2.3.5",
|
"@types/md5": "^2.3.5",
|
||||||
"@types/vue-cropperjs": "^4.1.6",
|
"@types/vue-cropperjs": "^4.1.6",
|
||||||
"@vicons/fluent": "^0.13.0",
|
"@vicons/fluent": "^0.13.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vueuse/core": "^13.3.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
"@vueuse/integrations": "^13.3.0",
|
"@vueuse/integrations": "^13.9.0",
|
||||||
"@vueuse/router": "^13.3.0",
|
"@vueuse/router": "^13.9.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"bilibili-live-ws": "^6.3.1",
|
"bilibili-live-ws": "^6.3.1",
|
||||||
"cropperjs": "^2.0.0",
|
"cropperjs": "^2.0.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"easy-speech": "^2.4.0",
|
"easy-speech": "^2.4.0",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^6.0.0",
|
||||||
"eslint-plugin-oxlint": "^0.16.12",
|
|
||||||
"fast-xml-parser": "^5.2.5",
|
"fast-xml-parser": "^5.2.5",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jszip": "^3.10.1",
|
|
||||||
"idb-keyval": "^6.2.2",
|
"idb-keyval": "^6.2.2",
|
||||||
"linqts": "^2.0.0",
|
"jszip": "^3.10.1",
|
||||||
|
"linqts": "^3.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.53.0",
|
||||||
"naive-ui": "^2.41.1",
|
"naive-ui": "^2.43.1",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.6",
|
||||||
"peerjs": "^1.5.5",
|
"peerjs": "^1.5.5",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"qrcode.vue": "^3.6.0",
|
"qrcode.vue": "^3.6.0",
|
||||||
"unplugin-auto-import": "^19.3.0",
|
"unplugin-auto-import": "^20.2.0",
|
||||||
"unplugin-vue-components": "^28.7.0",
|
"unplugin-vue-components": "^29.1.0",
|
||||||
"unplugin-vue-markdown": "^28.3.1",
|
"unplugin-vue-markdown": "^29.2.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^13.0.0",
|
||||||
"vite": "6.3.4",
|
"vite": "7.1.7",
|
||||||
"vite-plugin-oxlint": "^1.3.3",
|
|
||||||
"vite-svg-loader": "^5.1.0",
|
"vite-svg-loader": "^5.1.0",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.22",
|
||||||
"vue-cropperjs": "^5.0.0",
|
"vue-cropperjs": "^5.0.0",
|
||||||
"vue-echarts": "^7.0.3",
|
"vue-echarts": "^8.0.0",
|
||||||
"vue-request": "^2.0.4",
|
"vue-request": "^2.0.4",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"vue-turnstile": "^1.0.11",
|
"vue-turnstile": "^1.0.11",
|
||||||
"vue3-aplayer": "^1.7.3",
|
"vue3-aplayer": "^1.7.3",
|
||||||
"vue3-marquee": "^4.2.2",
|
"vue3-marquee": "^4.2.2",
|
||||||
"vueuc": "^0.4.64",
|
"vueuc": "^0.4.65",
|
||||||
"worker-timers": "^8.0.22",
|
"worker-timers": "^8.0.25",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.2.16",
|
"@antfu/eslint-config": "^5.4.1",
|
||||||
|
"@types/bun": "^1.2.23",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/jszip": "^3.4.1",
|
"@types/jszip": "^3.4.1",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^11.0.0",
|
||||||
"@vicons/ionicons5": "^0.13.0",
|
"@vicons/ionicons5": "^0.13.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.2.0",
|
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||||
"@vue-vine/eslint-config": "^0.2.20",
|
"@vue-vine/eslint-config": "^1.1.9",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.36.0",
|
||||||
"eslint-plugin-vue": "^10.2.0",
|
|
||||||
"stylus": "^0.64.0",
|
"stylus": "^0.64.0",
|
||||||
"typescript": "^5.9.0-dev.20250614",
|
"typescript": "^5.9.2",
|
||||||
"vue-vine": "^0.4.4"
|
"vue-vine": "^1.7.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,13 +413,22 @@ export interface LotteryUserCardInfo {
|
|||||||
export interface ScheduleWeekInfo {
|
export interface ScheduleWeekInfo {
|
||||||
year: number
|
year: number
|
||||||
week: number
|
week: number
|
||||||
days: ScheduleDayInfo[]
|
days: ScheduleDayInfo[][]
|
||||||
}
|
}
|
||||||
export interface ScheduleDayInfo {
|
export interface ScheduleDayInfo {
|
||||||
title: string | null
|
title: string | null
|
||||||
tag: string | null
|
tag: string | null
|
||||||
tagColor: string | null
|
tagColor: string | null
|
||||||
time: string | null
|
time: string | null
|
||||||
|
id: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchScheduleRequest {
|
||||||
|
startYear: number
|
||||||
|
startWeek: number
|
||||||
|
count: number
|
||||||
|
dayOfWeek: number
|
||||||
|
schedule: ScheduleDayInfo
|
||||||
}
|
}
|
||||||
export enum ThemeType {
|
export enum ThemeType {
|
||||||
Auto = 'auto',
|
Auto = 'auto',
|
||||||
|
|||||||
5
src/auto-imports.d.ts
vendored
5
src/auto-imports.d.ts
vendored
@@ -115,6 +115,7 @@ declare global {
|
|||||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
|
||||||
const getDate: typeof import('date-fns')['getDate']
|
const getDate: typeof import('date-fns')['getDate']
|
||||||
const getDay: typeof import('date-fns')['getDay']
|
const getDay: typeof import('date-fns')['getDay']
|
||||||
const getDayOfYear: typeof import('date-fns')['getDayOfYear']
|
const getDayOfYear: typeof import('date-fns')['getDayOfYear']
|
||||||
@@ -179,6 +180,7 @@ declare global {
|
|||||||
const isSameWeek: typeof import('date-fns')['isSameWeek']
|
const isSameWeek: typeof import('date-fns')['isSameWeek']
|
||||||
const isSameYear: typeof import('date-fns')['isSameYear']
|
const isSameYear: typeof import('date-fns')['isSameYear']
|
||||||
const isSaturday: typeof import('date-fns')['isSaturday']
|
const isSaturday: typeof import('date-fns')['isSaturday']
|
||||||
|
const isShallow: typeof import('vue')['isShallow']
|
||||||
const isSunday: typeof import('date-fns')['isSunday']
|
const isSunday: typeof import('date-fns')['isSunday']
|
||||||
const isThisHour: typeof import('date-fns')['isThisHour']
|
const isThisHour: typeof import('date-fns')['isThisHour']
|
||||||
const isThisISOWeek: typeof import('date-fns')['isThisISOWeek']
|
const isThisISOWeek: typeof import('date-fns')['isThisISOWeek']
|
||||||
@@ -507,6 +509,7 @@ declare global {
|
|||||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||||
|
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
|
||||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||||
@@ -553,6 +556,6 @@ declare global {
|
|||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
import('vue')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/components.d.ts
vendored
14
src/components.d.ts
vendored
@@ -19,27 +19,14 @@ declare module 'vue' {
|
|||||||
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
||||||
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
||||||
NAlert: typeof import('naive-ui')['NAlert']
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
|
||||||
NCard: typeof import('naive-ui')['NCard']
|
NCard: typeof import('naive-ui')['NCard']
|
||||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
|
||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
|
||||||
NFlex: typeof import('naive-ui')['NFlex']
|
NFlex: typeof import('naive-ui')['NFlex']
|
||||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
|
||||||
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
|
|
||||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
|
||||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
NText: typeof import('naive-ui')['NText']
|
NText: typeof import('naive-ui')['NText']
|
||||||
NTime: typeof import('naive-ui')['NTime']
|
|
||||||
PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default']
|
PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default']
|
||||||
PointHistoryCard: typeof import('./components/manage/PointHistoryCard.vue')['default']
|
PointHistoryCard: typeof import('./components/manage/PointHistoryCard.vue')['default']
|
||||||
PointOrderCard: typeof import('./components/manage/PointOrderCard.vue')['default']
|
PointOrderCard: typeof import('./components/manage/PointOrderCard.vue')['default']
|
||||||
@@ -55,7 +42,6 @@ declare module 'vue' {
|
|||||||
SongList: typeof import('./components/SongList.vue')['default']
|
SongList: typeof import('./components/SongList.vue')['default']
|
||||||
SongPlayer: typeof import('./components/SongPlayer.vue')['default']
|
SongPlayer: typeof import('./components/SongPlayer.vue')['default']
|
||||||
TempComponent: typeof import('./components/TempComponent.vue')['default']
|
TempComponent: typeof import('./components/TempComponent.vue')['default']
|
||||||
ToolDynamicNineGrid: typeof import('./components/manage/tools/ToolDynamicNineGrid.vue')['default']
|
|
||||||
TurnstileVerify: typeof import('./components/TurnstileVerify.vue')['default']
|
TurnstileVerify: typeof import('./components/TurnstileVerify.vue')['default']
|
||||||
UpdateNoteContainer: typeof import('./components/UpdateNoteContainer.vue')['default']
|
UpdateNoteContainer: typeof import('./components/UpdateNoteContainer.vue')['default']
|
||||||
UserBasicInfoCard: typeof import('./components/UserBasicInfoCard.vue')['default']
|
UserBasicInfoCard: typeof import('./components/UserBasicInfoCard.vue')['default']
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ScheduleWeekInfo } from '@/api/api-models'
|
import { ScheduleWeekInfo, ScheduleDayInfo } from '@/api/api-models'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { NBadge, NButton, NCard, NEllipsis, NEmpty, NGrid, NGridItem, NList, NListItem, NPopconfirm, NSpace, NText, NTime } from 'naive-ui'
|
import { NBadge, NButton, NCard, NEllipsis, NEmpty, NGrid, NGridItem, NIcon, NList, NListItem, NPopconfirm, NSpace, NText, NTime, useThemeVars } from 'naive-ui'
|
||||||
|
import { Clock20Regular, Bed20Regular } from '@vicons/fluent'
|
||||||
|
import { h } from 'vue'
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
const weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
const weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||||
function getDateFromWeek(year: number, week: number, dayOfWeek: number): Date {
|
function getDateFromWeek(year: number, week: number, dayOfWeek: number): Date {
|
||||||
@@ -24,6 +27,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'onUpdate', schedule: ScheduleWeekInfo): void
|
(e: 'onUpdate', schedule: ScheduleWeekInfo): void
|
||||||
(e: 'onDelete', schedule: ScheduleWeekInfo): void
|
(e: 'onDelete', schedule: ScheduleWeekInfo): void
|
||||||
(e: 'onCopy', schedule: ScheduleWeekInfo): void
|
(e: 'onCopy', schedule: ScheduleWeekInfo): void
|
||||||
|
(e: 'onEditItem', schedule: ScheduleWeekInfo, dayIndex: number, item: ScheduleDayInfo): void
|
||||||
|
(e: 'onDeleteItem', schedule: ScheduleWeekInfo, dayIndex: number, item: ScheduleDayInfo): void
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -78,67 +83,199 @@ const emit = defineEmits<{
|
|||||||
</template>
|
</template>
|
||||||
<NGrid
|
<NGrid
|
||||||
x-gap="8"
|
x-gap="8"
|
||||||
|
y-gap="8"
|
||||||
cols="1 1200:7"
|
cols="1 1200:7"
|
||||||
|
style="align-items: stretch;"
|
||||||
>
|
>
|
||||||
<NGridItem
|
<NGridItem
|
||||||
v-for="(day, index) in item.days"
|
v-for="(daySchedules, index) in item.days"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
style="display: flex;"
|
||||||
>
|
>
|
||||||
<NCard
|
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
|
||||||
size="small"
|
<div
|
||||||
:style="{ height: '65px', backgroundColor: day.tagColor + '1f' }"
|
:style="{
|
||||||
content-style="padding: 5px;"
|
marginBottom: '6px',
|
||||||
header-style="padding: 0px 6px 0px 6px;"
|
padding: '4px 8px',
|
||||||
:embedded="day?.tag != undefined"
|
fontSize: '13px',
|
||||||
>
|
fontWeight: '600',
|
||||||
<template #header-extra>
|
background: `linear-gradient(135deg, ${themeVars.primaryColorSuppl}15 0%, ${themeVars.primaryColorSuppl}25 100%)`,
|
||||||
<template v-if="day.tag">
|
borderRadius: '4px',
|
||||||
<NSpace :size="5">
|
display: 'flex',
|
||||||
<NBadge
|
alignItems: 'center',
|
||||||
v-if="day.tagColor"
|
gap: '6px',
|
||||||
dot
|
}"
|
||||||
:color="day.tagColor"
|
>
|
||||||
/>
|
<NTime
|
||||||
<NEllipsis>
|
:time="getDateFromWeek(item.year, item.week, index)"
|
||||||
<NText :style="{ color: day.tagColor }">
|
format="MM/dd"
|
||||||
{{ day.tag }}
|
:style="{ color: themeVars.primaryColor }"
|
||||||
</NText>
|
/>
|
||||||
</NEllipsis>
|
<NText>{{ weekdays[index] }}</NText>
|
||||||
</NSpace>
|
</div>
|
||||||
</template>
|
<div style="flex: 1; display: flex; flex-direction: column; min-height: 65px;">
|
||||||
<template v-else>
|
<NCard
|
||||||
<NText
|
v-if="daySchedules.length === 0"
|
||||||
depth="3"
|
size="small"
|
||||||
style="font-size: 11px"
|
:style="{
|
||||||
italic
|
minHeight: '40px',
|
||||||
>
|
background: `linear-gradient(135deg, ${themeVars.cardColor} 0%, ${themeVars.bodyColor} 100%)`,
|
||||||
休息
|
border: `1px dashed ${themeVars.dividerColor}`,
|
||||||
</NText>
|
cursor: isSelf ? 'pointer' : 'default',
|
||||||
</template>
|
transition: 'all 0.2s ease',
|
||||||
</template>
|
}"
|
||||||
<template #header>
|
:hoverable="isSelf"
|
||||||
|
content-style="display: flex; align-items: center; justify-content: center; gap: 4px;"
|
||||||
|
@click="isSelf && $emit('onUpdate', item)"
|
||||||
|
>
|
||||||
|
<NIcon :size="14" :component="Bed20Regular" :color="themeVars.textColor3" />
|
||||||
<NText
|
<NText
|
||||||
:depth="3"
|
depth="3"
|
||||||
style="font-size: 12px"
|
style="font-size: 11px; font-style: italic;"
|
||||||
strong
|
:style="{ opacity: isSelf ? 0.5 : 0.6 }"
|
||||||
:italic="!day.tag"
|
|
||||||
>
|
>
|
||||||
<NTime
|
休息
|
||||||
:time="getDateFromWeek(item.year, item.week, index)"
|
|
||||||
format="MM/dd"
|
|
||||||
/>
|
|
||||||
{{ weekdays[index] }}
|
|
||||||
{{ day.time }}
|
|
||||||
</NText>
|
</NText>
|
||||||
</template>
|
</NCard>
|
||||||
<template v-if="day?.title">
|
<NSpace
|
||||||
<NEllipsis>
|
v-else
|
||||||
<NText style="font-size: 13px">
|
vertical
|
||||||
{{ day.title }}
|
:size="4"
|
||||||
</NText>
|
>
|
||||||
</NEllipsis>
|
<NCard
|
||||||
</template>
|
v-for="(schedule, scheduleIndex) in daySchedules"
|
||||||
</NCard>
|
:key="schedule.id || `${index}-${scheduleIndex}`"
|
||||||
|
size="small"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: schedule.tagColor ? schedule.tagColor + '12' : themeVars.cardColor,
|
||||||
|
borderLeft: schedule.tagColor ? `3px solid ${schedule.tagColor}` : `3px solid ${themeVars.dividerColor}`,
|
||||||
|
cursor: isSelf ? 'pointer' : 'default',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
padding: '0'
|
||||||
|
}"
|
||||||
|
:bordered="true"
|
||||||
|
:hoverable="isSelf"
|
||||||
|
content-style="padding: 3px; padding-left: 5px;padding-bottom: 1px;"
|
||||||
|
@click="isSelf && $emit('onEditItem', item, index, schedule)"
|
||||||
|
>
|
||||||
|
<div style="padding: 4px 6px;">
|
||||||
|
<!-- 标签和时间行 (仅当有标签或时间时显示) -->
|
||||||
|
<div
|
||||||
|
v-if="schedule.tag || schedule.time"
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- 标签 -->
|
||||||
|
<div
|
||||||
|
v-if="schedule.tag"
|
||||||
|
:style="{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '3px',
|
||||||
|
padding: '1px 5px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
backgroundColor: schedule.tagColor ? schedule.tagColor + '22' : themeVars.primaryColorSuppl + '22',
|
||||||
|
flexShrink: 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NBadge
|
||||||
|
v-if="schedule.tagColor"
|
||||||
|
dot
|
||||||
|
:color="schedule.tagColor"
|
||||||
|
:style="{ transform: 'scale(0.85)' }"
|
||||||
|
/>
|
||||||
|
<NText
|
||||||
|
:style="{
|
||||||
|
color: schedule.tagColor || themeVars.primaryColor,
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: '10.5px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
lineHeight: '1.2'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ schedule.tag }}
|
||||||
|
</NText>
|
||||||
|
</div>
|
||||||
|
<!-- 时间 -->
|
||||||
|
<div
|
||||||
|
v-if="schedule.time"
|
||||||
|
:style="{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '2px',
|
||||||
|
flexShrink: 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NIcon :size="12" :component="Clock20Regular" :color="themeVars.textColor3" />
|
||||||
|
<NText
|
||||||
|
depth="2"
|
||||||
|
:style="{
|
||||||
|
fontSize: '10.5px',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontWeight: '500'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ schedule.time }}
|
||||||
|
</NText>
|
||||||
|
</div>
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<NButton
|
||||||
|
v-if="isSelf"
|
||||||
|
size="tiny"
|
||||||
|
type="error"
|
||||||
|
quaternary
|
||||||
|
circle
|
||||||
|
style="margin-left: auto; flex-shrink: 0; width: 18px; height: 18px; padding: 0;"
|
||||||
|
@click.stop="$emit('onDeleteItem', item, index, schedule)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<span style="font-size: 14px; line-height: 1;">×</span>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<!-- 内容 -->
|
||||||
|
<div v-if="schedule?.title">
|
||||||
|
<NEllipsis :line-clamp="2">
|
||||||
|
<NText
|
||||||
|
:style="{
|
||||||
|
fontSize: '12.5px',
|
||||||
|
lineHeight: '1.4',
|
||||||
|
color: themeVars.textColor2
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ schedule.title }}
|
||||||
|
</NText>
|
||||||
|
</NEllipsis>
|
||||||
|
</div>
|
||||||
|
<!-- 如果既没有标签也没有时间,但有删除按钮 -->
|
||||||
|
<div
|
||||||
|
v-if="!schedule.tag && !schedule.time && isSelf && !schedule?.title"
|
||||||
|
style="display: flex; justify-content: flex-end;"
|
||||||
|
>
|
||||||
|
<NButton
|
||||||
|
size="tiny"
|
||||||
|
type="error"
|
||||||
|
quaternary
|
||||||
|
circle
|
||||||
|
style="width: 18px; height: 18px; padding: 0;"
|
||||||
|
@click.stop="$emit('onDeleteItem', item, index, schedule)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<span style="font-size: 14px; line-height: 1;">×</span>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
VideoAdd20Filled,
|
VideoAdd20Filled,
|
||||||
Mail24Filled,
|
Mail24Filled,
|
||||||
} from '@vicons/fluent'
|
} from '@vicons/fluent'
|
||||||
import { AnalyticsSharp, BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, Eye, PlayForward, PlayBack, Play, Pause, VolumeHigh, ChevronUp, ChevronDown, TrashBin } from '@vicons/ionicons5'
|
import { AnalyticsSharp, BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, Eye, PlayForward, PlayBack, Play, Pause, VolumeHigh, ChevronUp, ChevronDown, TrashBin, Bookmark, BookmarkOutline } from '@vicons/ionicons5'
|
||||||
import { useElementSize, useStorage } from '@vueuse/core'
|
import { useElementSize, useStorage } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
NAlert,
|
NAlert,
|
||||||
@@ -68,6 +68,50 @@ const themeType = useStorage('Settings.Theme', ThemeType.Auto)
|
|||||||
|
|
||||||
// 收藏功能相关
|
// 收藏功能相关
|
||||||
const favoriteMenuItems = useStorage<string[]>('Settings.FavoriteMenuItems', [])
|
const favoriteMenuItems = useStorage<string[]>('Settings.FavoriteMenuItems', [])
|
||||||
|
const isFavorite = (key: string) => favoriteMenuItems.value?.includes(key)
|
||||||
|
const toggleFavorite = (key: string) => {
|
||||||
|
const list = favoriteMenuItems.value ?? []
|
||||||
|
const idx = list.indexOf(key)
|
||||||
|
if (idx === -1) list.unshift(key)
|
||||||
|
else list.splice(idx, 1)
|
||||||
|
favoriteMenuItems.value = [...list]
|
||||||
|
}
|
||||||
|
const renderFavoriteExtra = (key: string) => () =>
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{ class: ['menu-fav', isFavorite(key) ? 'active' : ''] },
|
||||||
|
[
|
||||||
|
h(
|
||||||
|
NTooltip,
|
||||||
|
{ placement: 'right' },
|
||||||
|
{
|
||||||
|
trigger: () =>
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
size: 'tiny',
|
||||||
|
circle: true,
|
||||||
|
onClick: (e: MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
toggleFavorite(key)
|
||||||
|
},
|
||||||
|
style: 'padding: 0; height: 18px; width: 18px;'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () =>
|
||||||
|
h(NIcon, {
|
||||||
|
component: isFavorite(key) ? Bookmark : BookmarkOutline,
|
||||||
|
size: 16,
|
||||||
|
color: isFavorite(key) ? '#f5c451' : undefined,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
default: () => (isFavorite(key) ? '取消收藏' : '收藏'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
// 侧边栏和布局相关
|
// 侧边栏和布局相关
|
||||||
const sider = ref()
|
const sider = ref()
|
||||||
@@ -130,259 +174,323 @@ const isBiliVerified = computed(() => accountInfo.value?.isBiliVerified)
|
|||||||
// 图标渲染函数 - 用于菜单项
|
// 图标渲染函数 - 用于菜单项
|
||||||
const renderIcon = (icon: any) => () => h(NIcon, null, { default: () => h(icon) })
|
const renderIcon = (icon: any) => () => h(NIcon, null, { default: () => h(icon) })
|
||||||
|
|
||||||
// 菜单配置
|
// 菜单配置(支持分组与收藏置顶)
|
||||||
const menuOptions = computed(() => {
|
const menuOptions = computed(() => {
|
||||||
return [
|
// 通用的菜单项工厂,自动挂载收藏按钮到叶子节点
|
||||||
{
|
const withFavoriteExtra = (item: any): any => {
|
||||||
|
if (item?.children?.length) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
children: item.children.map(withFavoriteExtra),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
extra: width.value >= 180 ? renderFavoriteExtra(item.key) : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonItems = [
|
||||||
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-history' } }, { default: () => '历史' }),
|
label: () => h(RouterLink, { to: { name: 'manage-history' } }, { default: () => '历史' }),
|
||||||
key: 'manage-history',
|
key: 'manage-history',
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
icon: renderIcon(AnalyticsSharp),
|
icon: renderIcon(AnalyticsSharp),
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-live' } }, { default: () => '直播记录' }),
|
label: () => h(RouterLink, { to: { name: 'manage-live' } }, { default: () => '直播记录' }),
|
||||||
key: 'manage-live',
|
key: 'manage-live',
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
icon: renderIcon(Live24Filled),
|
icon: renderIcon(Live24Filled),
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-analyze' } }, { default: () => '直播数据' }),
|
label: () => h(RouterLink, { to: { name: 'manage-analyze' } }, { default: () => '直播数据' }),
|
||||||
key: 'manage-analyze',
|
key: 'manage-analyze',
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
icon: renderIcon(Eye),
|
icon: renderIcon(Eye),
|
||||||
},
|
}),
|
||||||
{
|
]
|
||||||
|
|
||||||
|
const dataItems = [
|
||||||
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-event' } }, { default: () => '舰长和SC' }),
|
label: () => h(RouterLink, { to: { name: 'manage-event' } }, { default: () => '舰长和SC' }),
|
||||||
key: 'manage-event',
|
key: 'manage-event',
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
icon: renderIcon(VehicleShip24Filled),
|
icon: renderIcon(VehicleShip24Filled),
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-point' } }, { default: () => '积分和礼物' }),
|
label: () => h(RouterLink, { to: { name: 'manage-point' } }, { default: () => '积分和礼物' }),
|
||||||
key: 'manage-point',
|
key: 'manage-point',
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
icon: renderIcon(BookCoins20Filled),
|
icon: renderIcon(BookCoins20Filled),
|
||||||
},
|
}),
|
||||||
{
|
]
|
||||||
|
|
||||||
|
const toolsItems = [
|
||||||
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-schedule' } }, { default: () => '日程' }),
|
label: () => h(RouterLink, { to: { name: 'manage-schedule' } }, { default: () => '日程' }),
|
||||||
key: 'manage-schedule',
|
key: 'manage-schedule',
|
||||||
icon: renderIcon(CalendarClock24Filled),
|
icon: renderIcon(CalendarClock24Filled),
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-songList' } }, { default: () => '歌单' }),
|
label: () => h(RouterLink, { to: { name: 'manage-songList' } }, { default: () => '歌单' }),
|
||||||
key: 'manage-songList',
|
key: 'manage-songList',
|
||||||
icon: renderIcon(MusicalNote),
|
icon: renderIcon(MusicalNote),
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-questionBox' } }, { default: () => '棉花糖 (提问箱' }),
|
label: () => h(RouterLink, { to: { name: 'manage-questionBox' } }, { default: () => '棉花糖 (提问箱' }),
|
||||||
key: 'manage-questionBox',
|
key: 'manage-questionBox',
|
||||||
icon: renderIcon(Chatbox),
|
icon: renderIcon(Chatbox),
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-videoCollect' } }, { default: () => '视频征集' }),
|
label: () => h(RouterLink, { to: { name: 'manage-videoCollect' } }, { default: () => '视频征集' }),
|
||||||
key: 'manage-videoCollect',
|
key: 'manage-videoCollect',
|
||||||
icon: renderIcon(VideoAdd20Filled),
|
icon: renderIcon(VideoAdd20Filled),
|
||||||
disabled: accountInfo.value?.isEmailVerified === false,
|
disabled: accountInfo.value?.isEmailVerified === false,
|
||||||
},
|
}),
|
||||||
{
|
withFavoriteExtra({
|
||||||
label: () => h(RouterLink, { to: { name: 'manage-lottery' } }, { default: () => '动态抽奖' }),
|
label: () => h(RouterLink, { to: { name: 'manage-lottery' } }, { default: () => '动态抽奖' }),
|
||||||
key: 'manage-lottery',
|
key: 'manage-lottery',
|
||||||
icon: renderIcon(Lottery24Filled),
|
icon: renderIcon(Lottery24Filled),
|
||||||
},
|
}),
|
||||||
{
|
|
||||||
label: () => h(
|
|
||||||
NTooltip,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
trigger: () => h(
|
|
||||||
NText,
|
|
||||||
() => [
|
|
||||||
'弹幕相关',
|
|
||||||
h(
|
|
||||||
NTooltip,
|
|
||||||
{ style: 'padding: 0;' },
|
|
||||||
{
|
|
||||||
trigger: () => h(NIcon, { component: Info24Filled }),
|
|
||||||
default: () => h(
|
|
||||||
NAlert,
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
size: 'small',
|
|
||||||
title: '可用性警告',
|
|
||||||
style: 'max-width: 600px;',
|
|
||||||
},
|
|
||||||
() => h('div', {}, [
|
|
||||||
' 当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见',
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
tag: 'a',
|
|
||||||
href: 'https://developer.chrome.com/blog/background_tabs/',
|
|
||||||
target: '_blank',
|
|
||||||
type: 'info',
|
|
||||||
},
|
|
||||||
() => '此文章',
|
|
||||||
),
|
|
||||||
'), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连, 不过还是有可能会遗漏事件',
|
|
||||||
h('br'),
|
|
||||||
'为避免这种情况, 建议注册本站账后使用',
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
type: 'primary',
|
|
||||||
text: true,
|
|
||||||
size: 'small',
|
|
||||||
tag: 'a',
|
|
||||||
href: 'https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
() => 'VtsuruEventFetcher',
|
|
||||||
),
|
|
||||||
', 否则请在使用功能时尽量保持网页在前台运行, 同时关闭浏览器的 页面休眠/内存节省 功能',
|
|
||||||
h('br'),
|
|
||||||
'Chrome: ',
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
type: 'info',
|
|
||||||
text: true,
|
|
||||||
size: 'small',
|
|
||||||
tag: 'a',
|
|
||||||
href: 'https://support.google.com/chrome/answer/12929150?hl=zh-Hans#zippy=%2C%E5%BC%80%E5%90%AF%E6%88%96%E5%85%B3%E9%97%AD%E7%9C%81%E5%86%85%E5%AD%98%E6%A8%A1%E5%BC%8F%2C%E8%AE%A9%E7%89%B9%E5%AE%9A%E7%BD%91%E7%AB%99%E4%BF%9D%E6%8C%81%E6%B4%BB%E5%8A%A8%E7%8A%B6%E6%80%81',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
() => '让特定网站保持活动状态',
|
|
||||||
),
|
|
||||||
', Edge: ',
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
type: 'info',
|
|
||||||
text: true,
|
|
||||||
size: 'small',
|
|
||||||
tag: 'a',
|
|
||||||
href: 'https://support.microsoft.com/zh-cn/topic/%E4%BA%86%E8%A7%A3-microsoft-edge-%E4%B8%AD%E7%9A%84%E6%80%A7%E8%83%BD%E5%8A%9F%E8%83%BD-7b36f363-2119-448a-8de6-375cfd88ab25',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
() => '永远不想进入睡眠状态的网站',
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
default: () => isBiliVerified.value
|
|
||||||
? '需要使用直播弹幕的功能'
|
|
||||||
: '你尚未进行 Bilibili 认证, 请前往面板进行绑定',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
key: 'manage-danmaku',
|
|
||||||
icon: renderIcon(Chat24Filled),
|
|
||||||
disabled: accountInfo.value?.isEmailVerified === false || !isBiliVerified.value,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: () => !isBiliVerified.value ? '弹幕机' : h(
|
|
||||||
NBadge,
|
|
||||||
{ value: '新', offset: [15, 12], type: 'info' },
|
|
||||||
() => h(
|
|
||||||
NTooltip,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
trigger: () => h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-danmuji' } },
|
|
||||||
{ default: () => '弹幕机' },
|
|
||||||
),
|
|
||||||
default: () => '兼容 blivechat 样式 (其实就是直接用的 blivechat 组件',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
key: 'manage-danmuji',
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
icon: renderIcon(Lottery24Filled),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => !isBiliVerified.value ? '抽奖' : h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-liveLottery' } },
|
|
||||||
{ default: () => '抽奖' },
|
|
||||||
),
|
|
||||||
key: 'manage-liveLottery',
|
|
||||||
icon: renderIcon(Lottery24Filled),
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => !isBiliVerified.value ? '点播' : h(
|
|
||||||
NTooltip,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
trigger: () => h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-liveRequest' } },
|
|
||||||
{ default: () => '点播' },
|
|
||||||
),
|
|
||||||
default: () => '歌势之类用的, 可以用来点歌或者跳舞什么的',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
key: 'manage-liveRequest',
|
|
||||||
icon: renderIcon(MusicalNote),
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => !isBiliVerified.value ? '点歌' : h(
|
|
||||||
NTooltip,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
trigger: () => h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-musicRequest' } },
|
|
||||||
{ default: () => '点歌' },
|
|
||||||
),
|
|
||||||
default: () => '就是传统的点歌机, 发弹幕后播放指定的歌曲',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
key: 'manage-musicRequest',
|
|
||||||
icon: renderIcon(MusicalNote),
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => !isBiliVerified.value ? '排队' : h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-liveQueue' } },
|
|
||||||
{ default: () => '排队' },
|
|
||||||
),
|
|
||||||
key: 'manage-liveQueue',
|
|
||||||
icon: renderIcon(PeopleQueue24Filled),
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => !isBiliVerified.value ? '读弹幕' : h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-speech' } },
|
|
||||||
{ default: () => '读弹幕' },
|
|
||||||
),
|
|
||||||
key: 'manage-speech',
|
|
||||||
icon: renderIcon(TabletSpeaker24Filled),
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
},
|
|
||||||
/*{
|
|
||||||
label: () => !isBiliVerified.value ? '弹幕投票' : h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: { name: 'manage-danmakuVote' } },
|
|
||||||
{ default: () => '弹幕投票' },
|
|
||||||
),
|
|
||||||
key: 'manage-danmakuVote',
|
|
||||||
icon: renderIcon(Chat24Filled),
|
|
||||||
disabled: !isBiliVerified.value,
|
|
||||||
},*/
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const danmakuItem = {
|
||||||
|
label: () => h(
|
||||||
|
NTooltip,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
trigger: () => h(
|
||||||
|
NText,
|
||||||
|
() => [
|
||||||
|
'弹幕相关',
|
||||||
|
h(
|
||||||
|
NTooltip,
|
||||||
|
{ style: 'padding: 0;' },
|
||||||
|
{
|
||||||
|
trigger: () => h(NIcon, { component: Info24Filled }),
|
||||||
|
default: () => h(
|
||||||
|
NAlert,
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
size: 'small',
|
||||||
|
title: '可用性警告',
|
||||||
|
style: 'max-width: 600px;',
|
||||||
|
},
|
||||||
|
() => h('div', {}, [
|
||||||
|
' 当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见',
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
tag: 'a',
|
||||||
|
href: 'https://developer.chrome.com/blog/background_tabs/',
|
||||||
|
target: '_blank',
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
() => '此文章',
|
||||||
|
),
|
||||||
|
'), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连, 不过还是有可能会遗漏事件',
|
||||||
|
h('br'),
|
||||||
|
'为避免这种情况, 建议注册本站账后使用',
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
text: true,
|
||||||
|
size: 'small',
|
||||||
|
tag: 'a',
|
||||||
|
href: 'https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
|
() => 'VtsuruEventFetcher',
|
||||||
|
),
|
||||||
|
', 否则请在使用功能时尽量保持网页在前台运行, 同时关闭浏览器的 页面休眠/内存节省 功能',
|
||||||
|
h('br'),
|
||||||
|
'Chrome: ',
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
text: true,
|
||||||
|
size: 'small',
|
||||||
|
tag: 'a',
|
||||||
|
href: 'https://support.google.com/chrome/answer/12929150?hl=zh-Hans#zippy=%2C%E5%BC%80%E5%90%AF%E6%88%96%E5%85%B3%E9%97%AD%E7%9C%81%E5%86%85%E5%AD%98%E6%A8%A1%E5%BC%8F%2C%E8%AE%A9%E7%89%B9%E5%AE%9A%E7%BD%91%E7%AB%99%E4%BF%9D%E6%8C%81%E6%B4%BB%E5%8A%A8%E7%8A%B6%E6%80%81',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
|
() => '让特定网站保持活动状态',
|
||||||
|
),
|
||||||
|
', Edge: ',
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
text: true,
|
||||||
|
size: 'small',
|
||||||
|
tag: 'a',
|
||||||
|
href: 'https://support.microsoft.com/zh-cn/topic/%E4%BA%86%E8%A7%A3-microsoft-edge-%E4%B8%AD%E7%9A%84%E6%80%A7%E8%83%BD%E5%8A%9F%E8%83%BD-7b36f363-2119-448a-8de6-375cfd88ab25',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
|
() => '永远不想进入睡眠状态的网站',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
default: () => (isBiliVerified.value
|
||||||
|
? '需要使用直播弹幕的功能'
|
||||||
|
: '你尚未进行 Bilibili 认证, 请前往面板进行绑定'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
key: 'manage-danmaku',
|
||||||
|
icon: renderIcon(Chat24Filled),
|
||||||
|
disabled: accountInfo.value?.isEmailVerified === false || !isBiliVerified.value,
|
||||||
|
children: [
|
||||||
|
withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '弹幕机' : h(NTooltip,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
trigger: () => h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-danmuji' } },
|
||||||
|
{ default: () => '弹幕机' },
|
||||||
|
),
|
||||||
|
default: () => '兼容 blivechat 样式 (其实就是直接用的 blivechat 组件',
|
||||||
|
}),
|
||||||
|
key: 'manage-danmuji',
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
icon: renderIcon(Lottery24Filled),
|
||||||
|
}),
|
||||||
|
withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '抽奖' : h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-liveLottery' } },
|
||||||
|
{ default: () => '抽奖' },
|
||||||
|
),
|
||||||
|
key: 'manage-liveLottery',
|
||||||
|
icon: renderIcon(Lottery24Filled),
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
}),
|
||||||
|
withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '点播' : h(
|
||||||
|
NTooltip,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
trigger: () => h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-liveRequest' } },
|
||||||
|
{ default: () => '点播' },
|
||||||
|
),
|
||||||
|
default: () => '歌势之类用的, 可以用来点歌或者跳舞什么的',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
key: 'manage-liveRequest',
|
||||||
|
icon: renderIcon(MusicalNote),
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
}),
|
||||||
|
withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '点歌' : h(
|
||||||
|
NTooltip,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
trigger: () => h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-musicRequest' } },
|
||||||
|
{ default: () => '点歌' },
|
||||||
|
),
|
||||||
|
default: () => '就是传统的点歌机, 发弹幕后播放指定的歌曲',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
key: 'manage-musicRequest',
|
||||||
|
icon: renderIcon(MusicalNote),
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
}),
|
||||||
|
withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '排队' : h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-liveQueue' } },
|
||||||
|
{ default: () => '排队' },
|
||||||
|
),
|
||||||
|
key: 'manage-liveQueue',
|
||||||
|
icon: renderIcon(PeopleQueue24Filled),
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
}),
|
||||||
|
withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '读弹幕' : h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-speech' } },
|
||||||
|
{ default: () => '读弹幕' },
|
||||||
|
),
|
||||||
|
key: 'manage-speech',
|
||||||
|
icon: renderIcon(TabletSpeaker24Filled),
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
}),
|
||||||
|
/*withFavoriteExtra({
|
||||||
|
label: () => !isBiliVerified.value ? '弹幕投票' : h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: { name: 'manage-danmakuVote' } },
|
||||||
|
{ default: () => '弹幕投票' },
|
||||||
|
),
|
||||||
|
key: 'manage-danmakuVote',
|
||||||
|
icon: renderIcon(Chat24Filled),
|
||||||
|
disabled: !isBiliVerified.value,
|
||||||
|
}),*/
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扁平化叶子项用于收藏置顶
|
||||||
|
const flattenLeaf = (items: any[]): any[] => {
|
||||||
|
const result: any[] = []
|
||||||
|
for (const it of items) {
|
||||||
|
if (it.children?.length) {
|
||||||
|
result.push(...flattenLeaf(it.children))
|
||||||
|
} else {
|
||||||
|
result.push(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const allLeaf = [
|
||||||
|
...flattenLeaf(commonItems),
|
||||||
|
...flattenLeaf(dataItems),
|
||||||
|
...flattenLeaf(toolsItems),
|
||||||
|
...flattenLeaf(danmakuItem.children ?? []),
|
||||||
|
]
|
||||||
|
const leafMap = new Map(allLeaf.map(i => [i.key, i]))
|
||||||
|
|
||||||
|
const favorites = (favoriteMenuItems.value ?? [])
|
||||||
|
.map(k => leafMap.get(k))
|
||||||
|
.filter(Boolean) as any[]
|
||||||
|
|
||||||
|
const notFav = (i: any) => !isFavorite(i.key)
|
||||||
|
const danmakuChildren = (danmakuItem.children ?? []).filter(notFav)
|
||||||
|
const danmakuForGroup = danmakuChildren.length > 0 ? { ...danmakuItem, children: danmakuChildren } : null
|
||||||
|
|
||||||
|
const groups: any[] = []
|
||||||
|
if (favorites.length > 0) {
|
||||||
|
groups.push({ type: 'group', key: 'group-favorites', label: '我的收藏', children: favorites })
|
||||||
|
}
|
||||||
|
if (commonItems.filter(notFav).length > 0) {
|
||||||
|
groups.push({ type: 'group', key: 'group-common', label: '常用', children: commonItems.filter(notFav) })
|
||||||
|
}
|
||||||
|
if (dataItems.filter(notFav).length > 0) {
|
||||||
|
groups.push({ type: 'group', key: 'group-data', label: '数据', children: dataItems.filter(notFav) })
|
||||||
|
}
|
||||||
|
const toolsGroupChildren = [
|
||||||
|
...(danmakuForGroup ? [danmakuForGroup] : []),
|
||||||
|
...toolsItems.filter(notFav),
|
||||||
|
]
|
||||||
|
if (toolsGroupChildren.length > 0) {
|
||||||
|
groups.push({ type: 'group', key: 'group-tools', label: '互动与工具', children: toolsGroupChildren })
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
})
|
})
|
||||||
|
|
||||||
// 重发验证邮件
|
// 重发验证邮件
|
||||||
@@ -590,11 +698,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 主导航菜单 -->
|
<!-- 主导航菜单 -->
|
||||||
<NMenu
|
<NMenu
|
||||||
|
class="manage-sider-menu"
|
||||||
style="margin-top: 12px"
|
style="margin-top: 12px"
|
||||||
:disabled="accountInfo?.isEmailVerified !== true"
|
:disabled="accountInfo?.isEmailVerified !== true"
|
||||||
:default-value="($route.meta.parent as string) ?? $route.name?.toString()"
|
:default-value="($route.meta.parent as string) ?? $route.name?.toString()"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:collapsed-icon-size="22"
|
:collapsed-icon-size="22"
|
||||||
|
:icon-size="16"
|
||||||
|
:root-indent="10"
|
||||||
|
:indent="12"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -1337,4 +1449,40 @@ onMounted(() => {
|
|||||||
.music-player-card .n-tag:hover {
|
.music-player-card .n-tag:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 侧边栏菜单收藏按钮与紧凑样式 */
|
||||||
|
:deep(.manage-sider-menu .menu-fav) {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 0.15s ease, width 0.15s ease, margin-left 0.15s ease;
|
||||||
|
pointer-events: none; /* 不阻挡文字区域点击 */
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.manage-sider-menu .n-menu-item:hover .menu-fav),
|
||||||
|
:deep(.manage-sider-menu .menu-fav.active) {
|
||||||
|
opacity: 1;
|
||||||
|
width: 18px;
|
||||||
|
margin-left: 6px;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.manage-sider-menu .menu-fav .n-button) {
|
||||||
|
padding: 0;
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 略微收紧图标与文本的间距,提升有效可读宽度 */
|
||||||
|
:deep(.manage-sider-menu .n-menu-item .n-menu-item-content .n-menu-item-content__icon) {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.manage-sider-menu .n-menu-item .n-menu-item-content) {
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||||
import { FunctionTypes, ScheduleWeekInfo } from '@/api/api-models'
|
import { FunctionTypes, ScheduleDayInfo, ScheduleWeekInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import ScheduleList from '@/components/ScheduleList.vue'
|
import ScheduleList from '@/components/ScheduleList.vue'
|
||||||
import { BASE_API_URL, CN_HOST, CURRENT_HOST, SCHEDULE_API_URL } from '@/data/constants'
|
import { CURRENT_HOST, SCHEDULE_API_URL } from '@/data/constants'
|
||||||
import { copyToClipboard } from '@/Utils'
|
import { copyToClipboard } from '@/Utils'
|
||||||
import { TagQuestionMark16Filled } from '@vicons/fluent'
|
import { TagQuestionMark16Filled } from '@vicons/fluent'
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import { addWeeks, endOfWeek, endOfYear, format, isBefore, startOfWeek, startOfYear } from 'date-fns'
|
import { addWeeks, endOfWeek, endOfYear, format, isBefore, startOfWeek, startOfYear } from 'date-fns'
|
||||||
import {
|
import {
|
||||||
NAlert,
|
NAlert,
|
||||||
NBadge,
|
NBadge,
|
||||||
NButton,
|
NButton,
|
||||||
|
NCard,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
NColorPicker,
|
NColorPicker,
|
||||||
NDivider,
|
NDivider,
|
||||||
NFlex,
|
NFlex,
|
||||||
|
NIcon,
|
||||||
NInput,
|
NInput,
|
||||||
NInputGroup,
|
NInputGroup,
|
||||||
NInputGroupLabel,
|
NInputGroupLabel,
|
||||||
@@ -24,12 +25,13 @@ import {
|
|||||||
NSpace,
|
NSpace,
|
||||||
NSpin,
|
NSpin,
|
||||||
NSwitch,
|
NSwitch,
|
||||||
|
NText,
|
||||||
NTimePicker,
|
NTimePicker,
|
||||||
NTooltip,
|
NTooltip,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { SelectMixedOption, SelectOption } from 'naive-ui/es/select/src/interface'
|
import { SelectMixedOption, SelectOption } from 'naive-ui/es/select/src/interface'
|
||||||
import { VNode, computed, h, onMounted, ref } from 'vue'
|
import { VNode, computed, h, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
user: {
|
user: {
|
||||||
@@ -76,33 +78,219 @@ const weekOptions = computed(() => {
|
|||||||
return weeks
|
return weeks
|
||||||
})
|
})
|
||||||
const dayOptions = computed(() => {
|
const dayOptions = computed(() => {
|
||||||
const days = [] as SelectMixedOption[]
|
const days: SelectMixedOption[] = []
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
try {
|
const entries = updateScheduleModel.value?.days?.[i] ?? []
|
||||||
days.push({
|
const count = entries.length
|
||||||
label: updateScheduleModel.value?.days[i].tag ? weekdays[i] + ' (已安排)' : weekdays[i],
|
days.push({
|
||||||
value: i,
|
label: count > 0 ? `${weekdays[i]} (共${count}项)` : weekdays[i],
|
||||||
})
|
value: i,
|
||||||
} catch (err) {
|
})
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return days
|
return days
|
||||||
})
|
})
|
||||||
const existTagOptions = computed(() => {
|
const existTagOptions = computed(() => {
|
||||||
const colors = [] as SelectMixedOption[]
|
const colors: SelectMixedOption[] = []
|
||||||
|
const exists = new Set<string>()
|
||||||
schedules.value?.forEach((s) => {
|
schedules.value?.forEach((s) => {
|
||||||
s.days.forEach((d) => {
|
s.days.forEach((dayList) => {
|
||||||
if (d.tag && !colors.find((c) => c.value == d.tagColor && c.label == d.tag)) {
|
dayList.forEach((item) => {
|
||||||
colors.push({
|
const tag = item.tag ?? ''
|
||||||
label: d.tag,
|
const color = normalizeColor(item.tagColor) ?? ''
|
||||||
value: d.tagColor ?? '',
|
if (tag) {
|
||||||
})
|
const key = `${tag}__${color}`
|
||||||
}
|
if (!exists.has(key)) {
|
||||||
|
exists.add(key)
|
||||||
|
colors.push({
|
||||||
|
label: tag,
|
||||||
|
value: color,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return colors
|
return colors
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function normalizeColor(color: any): string | null {
|
||||||
|
// 如果是 null 或 undefined,返回 null
|
||||||
|
if (color == null) return null
|
||||||
|
|
||||||
|
// 将 HSL 转 RGB,返回 #RRGGBB
|
||||||
|
const hslToHex = (h: number, s: number, l: number) => {
|
||||||
|
const hue = ((h % 360) + 360) % 360 / 360 // 归一化到 [0, 1)
|
||||||
|
const saturation = Math.min(Math.max(s / 100, 0), 1)
|
||||||
|
const lightness = Math.min(Math.max(l / 100, 0), 1)
|
||||||
|
|
||||||
|
const hue2rgb = (p: number, q: number, t: number) => {
|
||||||
|
if (t < 0) t += 1
|
||||||
|
if (t > 1) t -= 1
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t
|
||||||
|
if (t < 1 / 2) return q
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
let r: number, g: number, b: number
|
||||||
|
if (saturation === 0) {
|
||||||
|
r = g = b = lightness
|
||||||
|
} else {
|
||||||
|
const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation
|
||||||
|
const p = 2 * lightness - q
|
||||||
|
r = hue2rgb(p, q, hue + 1 / 3)
|
||||||
|
g = hue2rgb(p, q, hue)
|
||||||
|
b = hue2rgb(p, q, hue - 1 / 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toHex = (c: number) => {
|
||||||
|
const hex = Math.round(Math.min(Math.max(c, 0), 1) * 255).toString(16)
|
||||||
|
return hex.length === 1 ? '0' + hex : hex
|
||||||
|
}
|
||||||
|
|
||||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是字符串,尝试解析并规范化
|
||||||
|
if (typeof color === 'string') {
|
||||||
|
const str = color.trim()
|
||||||
|
|
||||||
|
// 1) 处理 hex,统一为 #RRGGBB 大写
|
||||||
|
if (str.startsWith('#')) {
|
||||||
|
const hex = str.replace('#', '')
|
||||||
|
if (hex.length === 3) {
|
||||||
|
const r = hex[0]
|
||||||
|
const g = hex[1]
|
||||||
|
const b = hex[2]
|
||||||
|
return (`#${r}${r}${g}${g}${b}${b}`).toUpperCase()
|
||||||
|
}
|
||||||
|
if (hex.length >= 6) {
|
||||||
|
return (`#${hex.substring(0, 6)}`).toUpperCase()
|
||||||
|
}
|
||||||
|
return str.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 处理 hsla/hsl 字符串
|
||||||
|
const hslMatch = str.match(/^hsla?\(\s*([+-]?\d+(?:\.\d+)?)\s*,\s*([+-]?\d+(?:\.\d+)?)%\s*,\s*([+-]?\d+(?:\.\d+)?)%\s*(?:,\s*([+-]?\d*(?:\.\d+)?)\s*)?\)$/i)
|
||||||
|
if (hslMatch) {
|
||||||
|
const h = parseFloat(hslMatch[1])
|
||||||
|
const s = parseFloat(hslMatch[2])
|
||||||
|
const l = parseFloat(hslMatch[3])
|
||||||
|
return hslToHex(h, s, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 处理 rgba/rgb 字符串,忽略 alpha
|
||||||
|
const rgbMatch = str.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([+-]?\d*(?:\.\d+)?)\s*)?\)$/i)
|
||||||
|
if (rgbMatch) {
|
||||||
|
const r = Math.min(255, Math.max(0, parseInt(rgbMatch[1])))
|
||||||
|
const g = Math.min(255, Math.max(0, parseInt(rgbMatch[2])))
|
||||||
|
const b = Math.min(255, Math.max(0, parseInt(rgbMatch[3])))
|
||||||
|
const toHex = (n: number) => n.toString(16).padStart(2, '0')
|
||||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) 其他字符串,原样返回(交由下游使用场景判断)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是数组([h, s, l, (a)] HS(L)A),转换为十六进制
|
||||||
|
if (Array.isArray(color) && color.length >= 3) {
|
||||||
|
const [h, s, l] = color
|
||||||
|
return hslToHex(Number(h), Number(s), Number(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyDay(): ScheduleDayInfo {
|
||||||
|
return {
|
||||||
|
title: null,
|
||||||
|
tag: null,
|
||||||
|
tagColor: null,
|
||||||
|
time: null,
|
||||||
|
id: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyDays(): ScheduleDayInfo[][] {
|
||||||
|
return Array.from({ length: 7 }, () => [] as ScheduleDayInfo[])
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeWeek(week?: ScheduleWeekInfo): ScheduleWeekInfo {
|
||||||
|
const normalizedDays = Array.from({ length: 7 }, (_, index) => {
|
||||||
|
const list = week?.days?.[index]
|
||||||
|
if (!Array.isArray(list)) return [] as ScheduleDayInfo[]
|
||||||
|
return list
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((item) => ({
|
||||||
|
title: item?.title ?? null,
|
||||||
|
tag: item?.tag ?? null,
|
||||||
|
tagColor: normalizeColor(item?.tagColor),
|
||||||
|
time: item?.time ?? null,
|
||||||
|
id: item?.id ?? null,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: week?.year ?? new Date().getFullYear(),
|
||||||
|
week: week?.week ?? Number(format(Date.now(), 'w')) + 1,
|
||||||
|
days: normalizedDays,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneWeek(week: ScheduleWeekInfo, options: { resetIds?: boolean } = {}): ScheduleWeekInfo {
|
||||||
|
// 深度克隆以完全断开响应式引用
|
||||||
|
const deepCloned = JSON.parse(JSON.stringify(week))
|
||||||
|
const normalized = normalizeWeek(deepCloned)
|
||||||
|
return {
|
||||||
|
year: normalized.year,
|
||||||
|
week: normalized.week,
|
||||||
|
days: normalized.days.map((dayList) =>
|
||||||
|
dayList.map((item) => ({
|
||||||
|
title: item.title ?? null,
|
||||||
|
tag: item.tag ?? null,
|
||||||
|
tagColor: normalizeColor(item.tagColor),
|
||||||
|
time: item.time ?? null,
|
||||||
|
id: options.resetIds ? null : item.id ?? null,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyWeek(year?: number, week?: number): ScheduleWeekInfo {
|
||||||
|
return {
|
||||||
|
year: year ?? new Date().getFullYear(),
|
||||||
|
week: week ?? Number(format(Date.now(), 'w')) + 1,
|
||||||
|
days: createEmptyDays(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDayInitialized(target: ScheduleWeekInfo, dayIndex: number) {
|
||||||
|
if (!target.days || !Array.isArray(target.days)) {
|
||||||
|
target.days = createEmptyDays()
|
||||||
|
}
|
||||||
|
if (!Array.isArray(target.days[dayIndex])) {
|
||||||
|
target.days[dayIndex] = []
|
||||||
|
}
|
||||||
|
if (target.days[dayIndex].length === 0) {
|
||||||
|
target.days[dayIndex].push(createEmptyDay())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeDays(days?: ScheduleDayInfo[][]): ScheduleDayInfo[][] {
|
||||||
|
return Array.from({ length: 7 }, (_, index) => {
|
||||||
|
const list = days?.[index] ?? []
|
||||||
|
return list
|
||||||
|
.filter((item) => !!item && (item.title?.trim() || item.tag?.trim() || item.time?.trim()))
|
||||||
|
.map((item) => ({
|
||||||
|
title: item.title?.trim() || null,
|
||||||
|
tag: item.tag?.trim() || null,
|
||||||
|
tagColor: normalizeColor(item.tagColor),
|
||||||
|
time: item.time?.trim() || null,
|
||||||
|
id: item.id ?? null,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
function getAllWeeks(year: number) {
|
function getAllWeeks(year: number) {
|
||||||
const startDate = startOfYear(new Date(year, 0, 1))
|
const startDate = startOfYear(new Date(year, 0, 1))
|
||||||
const endDate = endOfYear(new Date(year, 11, 31))
|
const endDate = endOfYear(new Date(year, 11, 31))
|
||||||
@@ -125,7 +313,7 @@ function getAllWeeks(year: number) {
|
|||||||
return weeks
|
return weeks
|
||||||
}
|
}
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
const schedules = ref<ScheduleWeekInfo[]>()
|
const schedules = ref<ScheduleWeekInfo[]>([])
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
@@ -133,13 +321,47 @@ const isLoading = ref(true)
|
|||||||
const showUpdateModal = ref(false)
|
const showUpdateModal = ref(false)
|
||||||
const showAddModal = ref(false)
|
const showAddModal = ref(false)
|
||||||
const showCopyModal = ref(false)
|
const showCopyModal = ref(false)
|
||||||
const updateScheduleModel = ref<ScheduleWeekInfo>({} as ScheduleWeekInfo)
|
const updateScheduleModel = ref<ScheduleWeekInfo>(createEmptyWeek())
|
||||||
const selectedExistTag = ref()
|
const selectedExistTag = ref()
|
||||||
|
const editingItemIndex = ref<number | null>(null)
|
||||||
|
|
||||||
const selectedDay = ref(0)
|
const selectedDay = ref(0)
|
||||||
const selectedScheduleYear = ref(new Date().getFullYear())
|
const selectedScheduleYear = ref(new Date().getFullYear())
|
||||||
const selectedScheduleWeek = ref(Number(format(Date.now(), 'w')) + 1)
|
const selectedScheduleWeek = ref(Number(format(Date.now(), 'w')) + 1)
|
||||||
|
|
||||||
|
watch(showUpdateModal, (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
|
// 清理所有可能的数组格式颜色值
|
||||||
|
updateScheduleModel.value.days.forEach(dayList => {
|
||||||
|
dayList.forEach(item => {
|
||||||
|
if (item.tagColor && Array.isArray(item.tagColor)) {
|
||||||
|
item.tagColor = normalizeColor(item.tagColor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 深度监听 updateScheduleModel 的 tagColor,确保它们始终是字符串格式
|
||||||
|
watch(
|
||||||
|
() => updateScheduleModel.value.days,
|
||||||
|
(days) => {
|
||||||
|
days?.forEach(dayList => {
|
||||||
|
dayList?.forEach(item => {
|
||||||
|
if (item.tagColor && Array.isArray(item.tagColor)) {
|
||||||
|
item.tagColor = normalizeColor(item.tagColor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(selectedDay, (value) => {
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, value)
|
||||||
|
})
|
||||||
|
|
||||||
async function get() {
|
async function get() {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
await QueryGetAPI<ScheduleWeekInfo[]>(SCHEDULE_API_URL + 'get', {
|
await QueryGetAPI<ScheduleWeekInfo[]>(SCHEDULE_API_URL + 'get', {
|
||||||
@@ -147,12 +369,12 @@ async function get() {
|
|||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
schedules.value = data.data
|
schedules.value = (data.data ?? []).map((week) => normalizeWeek(week))
|
||||||
} else {
|
} else {
|
||||||
message.error('加载失败: ' + data.message)
|
message.error('加载失败: ' + data.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(() => {
|
||||||
message.error('加载失败')
|
message.error('加载失败')
|
||||||
})
|
})
|
||||||
.finally(() => (isLoading.value = false))
|
.finally(() => (isLoading.value = false))
|
||||||
@@ -160,15 +382,17 @@ async function get() {
|
|||||||
const isFetching = ref(false)
|
const isFetching = ref(false)
|
||||||
async function addSchedule() {
|
async function addSchedule() {
|
||||||
isFetching.value = true
|
isFetching.value = true
|
||||||
|
const emptyWeek = createEmptyWeek(selectedScheduleYear.value, selectedScheduleWeek.value)
|
||||||
await QueryPostAPI(SCHEDULE_API_URL + 'update', {
|
await QueryPostAPI(SCHEDULE_API_URL + 'update', {
|
||||||
year: selectedScheduleYear.value,
|
year: emptyWeek.year,
|
||||||
week: selectedScheduleWeek.value,
|
week: emptyWeek.week,
|
||||||
|
days: emptyWeek.days,
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('添加成功')
|
message.success('添加成功')
|
||||||
showAddModal.value = false
|
showAddModal.value = false
|
||||||
get()
|
schedules.value = [...schedules.value, emptyWeek]
|
||||||
} else {
|
} else {
|
||||||
message.error('添加失败: ' + data.message)
|
message.error('添加失败: ' + data.message)
|
||||||
}
|
}
|
||||||
@@ -183,30 +407,60 @@ async function onCopySchedule() {
|
|||||||
} else {
|
} else {
|
||||||
updateScheduleModel.value.year = selectedScheduleYear.value
|
updateScheduleModel.value.year = selectedScheduleYear.value
|
||||||
updateScheduleModel.value.week = selectedScheduleWeek.value
|
updateScheduleModel.value.week = selectedScheduleWeek.value
|
||||||
await onUpdateSchedule()
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
|
await saveSchedule(null)
|
||||||
showCopyModal.value = false
|
showCopyModal.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onUpdateSchedule() {
|
async function saveSchedule(day: number | null) {
|
||||||
isFetching.value = true
|
isFetching.value = true
|
||||||
await QueryPostAPI(SCHEDULE_API_URL + 'update', {
|
const sanitizedDays = sanitizeDays(updateScheduleModel.value.days)
|
||||||
|
const payload: {
|
||||||
|
year: number
|
||||||
|
week: number
|
||||||
|
day?: number
|
||||||
|
days: ScheduleDayInfo[][]
|
||||||
|
} = {
|
||||||
year: updateScheduleModel.value.year,
|
year: updateScheduleModel.value.year,
|
||||||
week: updateScheduleModel.value.week,
|
week: updateScheduleModel.value.week,
|
||||||
day: selectedDay.value,
|
days: sanitizedDays,
|
||||||
days: updateScheduleModel.value?.days,
|
}
|
||||||
})
|
|
||||||
|
if (day !== null && day !== undefined) {
|
||||||
|
payload.day = day
|
||||||
|
}
|
||||||
|
|
||||||
|
await QueryPostAPI(SCHEDULE_API_URL + 'update', payload)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('成功')
|
message.success('成功')
|
||||||
const s = schedules.value?.find(
|
const normalizedWeek = normalizeWeek({
|
||||||
(s) => s.year == selectedScheduleYear.value && s.week == selectedScheduleWeek.value,
|
year: payload.year,
|
||||||
|
week: payload.week,
|
||||||
|
days: sanitizedDays,
|
||||||
|
})
|
||||||
|
|
||||||
|
const index = schedules.value.findIndex(
|
||||||
|
(s) => s.year == updateScheduleModel.value.year && s.week == updateScheduleModel.value.week,
|
||||||
)
|
)
|
||||||
if (s) {
|
|
||||||
s.days[selectedDay.value] = updateScheduleModel.value.days[selectedDay.value]
|
if (index >= 0) {
|
||||||
|
if (day !== null && day !== undefined) {
|
||||||
|
const current = cloneWeek(schedules.value[index])
|
||||||
|
current.days[day] = normalizedWeek.days[day]
|
||||||
|
schedules.value.splice(index, 1, current)
|
||||||
|
} else {
|
||||||
|
schedules.value.splice(index, 1, normalizedWeek)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
schedules.value?.push(updateScheduleModel.value)
|
schedules.value.push(normalizedWeek)
|
||||||
}
|
}
|
||||||
//updateScheduleModel.value = {} as ScheduleWeekInfo
|
updateScheduleModel.value = normalizeWeek({
|
||||||
|
year: payload.year,
|
||||||
|
week: payload.week,
|
||||||
|
days: sanitizedDays,
|
||||||
|
})
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
} else {
|
} else {
|
||||||
message.error('修改失败: ' + data.message)
|
message.error('修改失败: ' + data.message)
|
||||||
}
|
}
|
||||||
@@ -215,6 +469,9 @@ async function onUpdateSchedule() {
|
|||||||
isFetching.value = false
|
isFetching.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function onUpdateSchedule() {
|
||||||
|
await saveSchedule(selectedDay.value)
|
||||||
|
}
|
||||||
async function onDeleteSchedule(schedule: ScheduleWeekInfo) {
|
async function onDeleteSchedule(schedule: ScheduleWeekInfo) {
|
||||||
await QueryGetAPI(SCHEDULE_API_URL + 'del', {
|
await QueryGetAPI(SCHEDULE_API_URL + 'del', {
|
||||||
year: schedule.year,
|
year: schedule.year,
|
||||||
@@ -229,19 +486,97 @@ async function onDeleteSchedule(schedule: ScheduleWeekInfo) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
function onOpenUpdateModal(schedule: ScheduleWeekInfo) {
|
function onOpenUpdateModal(schedule: ScheduleWeekInfo) {
|
||||||
updateScheduleModel.value = JSON.parse(JSON.stringify(schedule))
|
updateScheduleModel.value = cloneWeek(schedule)
|
||||||
|
selectedDay.value = 0
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
showUpdateModal.value = true
|
showUpdateModal.value = true
|
||||||
}
|
}
|
||||||
function onOpenCopyModal(schedule: ScheduleWeekInfo) {
|
function onOpenCopyModal(schedule: ScheduleWeekInfo) {
|
||||||
updateScheduleModel.value = JSON.parse(JSON.stringify(schedule))
|
updateScheduleModel.value = cloneWeek(schedule, { resetIds: true })
|
||||||
|
selectedDay.value = 0
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
showCopyModal.value = true
|
showCopyModal.value = true
|
||||||
}
|
}
|
||||||
function onSelectChange(value: string | null, option: SelectMixedOption) {
|
function onEditScheduleItem(schedule: ScheduleWeekInfo, dayIndex: number, item: ScheduleDayInfo) {
|
||||||
|
updateScheduleModel.value = cloneWeek(schedule)
|
||||||
|
selectedDay.value = dayIndex
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, dayIndex)
|
||||||
|
showUpdateModal.value = true
|
||||||
|
}
|
||||||
|
async function onDeleteScheduleItem(schedule: ScheduleWeekInfo, dayIndex: number, item: ScheduleDayInfo) {
|
||||||
|
const targetSchedule = schedules.value.find(s => s.year === schedule.year && s.week === schedule.week)
|
||||||
|
if (!targetSchedule) return
|
||||||
|
|
||||||
|
const itemIndex = targetSchedule.days[dayIndex].findIndex(i =>
|
||||||
|
i.id === item.id || (i.title === item.title && i.time === item.time && i.tag === item.tag)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (itemIndex === -1) return
|
||||||
|
|
||||||
|
const updatedDays = targetSchedule.days.map((dayList, idx) => {
|
||||||
|
if (idx === dayIndex) {
|
||||||
|
return dayList.filter((_, i) => i !== itemIndex)
|
||||||
|
}
|
||||||
|
return dayList
|
||||||
|
})
|
||||||
|
|
||||||
|
await QueryPostAPI(SCHEDULE_API_URL + 'update', {
|
||||||
|
year: schedule.year,
|
||||||
|
week: schedule.week,
|
||||||
|
days: sanitizeDays(updatedDays),
|
||||||
|
}).then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
message.success('已删除')
|
||||||
|
const index = schedules.value.findIndex(s => s.year === schedule.year && s.week === schedule.week)
|
||||||
|
if (index >= 0) {
|
||||||
|
schedules.value[index] = normalizeWeek({
|
||||||
|
year: schedule.year,
|
||||||
|
week: schedule.week,
|
||||||
|
days: updatedDays,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error('删除失败: ' + data.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function onSelectChange(value: string | null, option: SelectMixedOption, itemIndex: number) {
|
||||||
if (value) {
|
if (value) {
|
||||||
updateScheduleModel.value.days[selectedDay.value].tagColor = value
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
updateScheduleModel.value.days[selectedDay.value].tag = option.label as string
|
const entry = updateScheduleModel.value.days[selectedDay.value][itemIndex]
|
||||||
|
if (entry) {
|
||||||
|
entry.tagColor = value
|
||||||
|
entry.tag = option.label as string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addScheduleItem() {
|
||||||
|
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||||
|
updateScheduleModel.value.days[selectedDay.value].push(createEmptyDay())
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeScheduleItem(index: number) {
|
||||||
|
const dayList = updateScheduleModel.value.days[selectedDay.value]
|
||||||
|
if (dayList && dayList.length > 0) {
|
||||||
|
dayList.splice(index, 1)
|
||||||
|
if (dayList.length === 0) {
|
||||||
|
dayList.push(createEmptyDay())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveScheduleItem(index: number, direction: 'up' | 'down') {
|
||||||
|
const dayList = updateScheduleModel.value.days[selectedDay.value]
|
||||||
|
if (!dayList) return
|
||||||
|
|
||||||
|
const targetIndex = direction === 'up' ? index - 1 : index + 1
|
||||||
|
if (targetIndex < 0 || targetIndex >= dayList.length) return
|
||||||
|
|
||||||
|
const temp = dayList[index]
|
||||||
|
dayList[index] = dayList[targetIndex]
|
||||||
|
dayList[targetIndex] = temp
|
||||||
|
}
|
||||||
const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
||||||
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [
|
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [
|
||||||
option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null,
|
option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null,
|
||||||
@@ -395,7 +730,7 @@ onMounted(() => {
|
|||||||
</NModal>
|
</NModal>
|
||||||
<NModal
|
<NModal
|
||||||
v-model:show="showUpdateModal"
|
v-model:show="showUpdateModal"
|
||||||
style="width: 600px; max-width: 90vw"
|
style="width: 800px; max-width: 95vw; max-height: 90vh;"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="编辑周程"
|
title="编辑周程"
|
||||||
>
|
>
|
||||||
@@ -405,60 +740,142 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<template v-if="updateScheduleModel">
|
<template v-if="updateScheduleModel">
|
||||||
<NSpace vertical>
|
<div
|
||||||
<NSpace>
|
style="
|
||||||
<NInputGroup>
|
max-height: calc(90vh - 300px);
|
||||||
<NInputGroupLabel type="primary">
|
overflow-y: auto;
|
||||||
标签
|
padding-right: 8px;
|
||||||
</NInputGroupLabel>
|
scrollbar-width: thin;
|
||||||
<NInput
|
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||||
v-model:value="updateScheduleModel.days[selectedDay].tag"
|
"
|
||||||
placeholder="标签 | 留空视为无安排"
|
>
|
||||||
style="max-width: 300px"
|
<NSpace
|
||||||
maxlength="10"
|
vertical
|
||||||
show-count
|
:size="12"
|
||||||
/>
|
|
||||||
</NInputGroup>
|
|
||||||
<NSelect
|
|
||||||
v-model:value="selectedExistTag"
|
|
||||||
:options="existTagOptions"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
placeholder="使用过的标签"
|
|
||||||
style="max-width: 150px"
|
|
||||||
:render-option="renderOption"
|
|
||||||
@update:value="onSelectChange"
|
|
||||||
/>
|
|
||||||
</NSpace>
|
|
||||||
<NInputGroup>
|
|
||||||
<NInputGroupLabel> 内容 </NInputGroupLabel>
|
|
||||||
<NInput
|
|
||||||
v-model:value="updateScheduleModel.days[selectedDay].title"
|
|
||||||
placeholder="内容"
|
|
||||||
style="max-width: 200px"
|
|
||||||
maxlength="30"
|
|
||||||
show-count
|
|
||||||
/>
|
|
||||||
</NInputGroup>
|
|
||||||
<NTimePicker
|
|
||||||
v-model:formatted-value="updateScheduleModel.days[selectedDay].time"
|
|
||||||
default-formatted-value="20:00"
|
|
||||||
format="HH:mm"
|
|
||||||
/>
|
|
||||||
<NColorPicker
|
|
||||||
v-model:value="updateScheduleModel.days[selectedDay].tagColor"
|
|
||||||
:swatches="['#FFFFFF', '#18A058', '#2080F0', '#F0A020', 'rgba(208, 48, 80, 1)']"
|
|
||||||
default-value="#61B589"
|
|
||||||
:show-alpha="false"
|
|
||||||
:modes="['hex']"
|
|
||||||
/>
|
|
||||||
<NButton
|
|
||||||
:loading="isFetching"
|
|
||||||
@click="onUpdateSchedule()"
|
|
||||||
>
|
>
|
||||||
保存
|
<NButton
|
||||||
</NButton>
|
type="primary"
|
||||||
</NSpace>
|
secondary
|
||||||
|
@click="addScheduleItem"
|
||||||
|
>
|
||||||
|
+ 添加行程项
|
||||||
|
</NButton>
|
||||||
|
<NCard
|
||||||
|
v-for="(item, itemIndex) in updateScheduleModel.days[selectedDay]"
|
||||||
|
:key="itemIndex"
|
||||||
|
size="small"
|
||||||
|
:bordered="true"
|
||||||
|
:style="{
|
||||||
|
borderLeft: item.tagColor ? `4px solid ${item.tagColor}` : 'none',
|
||||||
|
backgroundColor: item.tagColor ? item.tagColor + '08' : 'transparent'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<NSpace align="center" :size="8">
|
||||||
|
<NText strong style="font-size: 14px;">行程 {{ itemIndex + 1 }}</NText>
|
||||||
|
<NButton
|
||||||
|
v-if="itemIndex > 0"
|
||||||
|
size="tiny"
|
||||||
|
quaternary
|
||||||
|
@click="moveScheduleItem(itemIndex, 'up')"
|
||||||
|
>
|
||||||
|
↑
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
v-if="itemIndex < updateScheduleModel.days[selectedDay].length - 1"
|
||||||
|
size="tiny"
|
||||||
|
quaternary
|
||||||
|
@click="moveScheduleItem(itemIndex, 'down')"
|
||||||
|
>
|
||||||
|
↓
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
<template #header-extra>
|
||||||
|
<NButton
|
||||||
|
size="tiny"
|
||||||
|
type="error"
|
||||||
|
quaternary
|
||||||
|
@click="removeScheduleItem(itemIndex)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
<NSpace
|
||||||
|
vertical
|
||||||
|
:size="12"
|
||||||
|
>
|
||||||
|
<NSpace align="center" :size="8" style="flex-wrap: wrap;">
|
||||||
|
<NInputGroup style="width: auto; min-width: 200px;">
|
||||||
|
<NInputGroupLabel type="primary" style="min-width: 50px;">
|
||||||
|
标签
|
||||||
|
</NInputGroupLabel>
|
||||||
|
<NInput
|
||||||
|
v-model:value="item.tag"
|
||||||
|
placeholder="标签名称"
|
||||||
|
style="width: 150px;"
|
||||||
|
maxlength="10"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</NInputGroup>
|
||||||
|
<NSelect
|
||||||
|
:value="null"
|
||||||
|
:options="existTagOptions"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="选择已用标签"
|
||||||
|
style="width: 140px;"
|
||||||
|
:render-option="renderOption"
|
||||||
|
@update:value="(val, opt) => onSelectChange(val, opt, itemIndex)"
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputGroupLabel style="min-width: 50px;"> 内容 </NInputGroupLabel>
|
||||||
|
<NInput
|
||||||
|
v-model:value="item.title"
|
||||||
|
placeholder="事件内容描述"
|
||||||
|
maxlength="50"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</NInputGroup>
|
||||||
|
<NSpace align="center" :size="8">
|
||||||
|
<NInputGroup style="width: auto;">
|
||||||
|
<NInputGroupLabel style="min-width: 50px;">时间</NInputGroupLabel>
|
||||||
|
<NTimePicker
|
||||||
|
v-model:formatted-value="item.time"
|
||||||
|
default-formatted-value="20:00"
|
||||||
|
format="HH:mm"
|
||||||
|
style="width: 120px"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</NInputGroup>
|
||||||
|
<NInputGroup style="width: auto;">
|
||||||
|
<NInputGroupLabel style="min-width: 50px;">颜色</NInputGroupLabel>
|
||||||
|
<NColorPicker
|
||||||
|
:key="`color-${selectedDay}-${itemIndex}-${item.id || 'new'}`"
|
||||||
|
:value="normalizeColor(item.tagColor)"
|
||||||
|
@update:value="(val) => item.tagColor = normalizeColor(val)"
|
||||||
|
:swatches="['#18A058', '#2080F0', '#F0A020', '#D03050', '#9333EA', '#14B8A6']"
|
||||||
|
default-value="#2080F0"
|
||||||
|
:show-alpha="false"
|
||||||
|
:modes="['hex']"
|
||||||
|
style="width: 120px;"
|
||||||
|
/>
|
||||||
|
</NInputGroup>
|
||||||
|
</NSpace>
|
||||||
|
</NSpace>
|
||||||
|
</NCard>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
<NDivider />
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
:loading="isFetching"
|
||||||
|
block
|
||||||
|
@click="onUpdateSchedule()"
|
||||||
|
>
|
||||||
|
保存全部
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
<NSpin
|
<NSpin
|
||||||
@@ -472,5 +889,39 @@ onMounted(() => {
|
|||||||
@on-update="onOpenUpdateModal"
|
@on-update="onOpenUpdateModal"
|
||||||
@on-delete="onDeleteSchedule"
|
@on-delete="onDeleteSchedule"
|
||||||
@on-copy="onOpenCopyModal"
|
@on-copy="onOpenCopyModal"
|
||||||
|
@on-edit-item="onEditScheduleItem"
|
||||||
|
@on-delete-item="onDeleteScheduleItem"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 自定义滚动条样式 - Webkit浏览器 */
|
||||||
|
div::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
div::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式下的滚动条 */
|
||||||
|
html.dark div::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark div::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -99,48 +99,55 @@
|
|||||||
year: 2023,
|
year: 2023,
|
||||||
week: 30,
|
week: 30,
|
||||||
days: [
|
days: [
|
||||||
{
|
[{
|
||||||
title: '唱唱歌!',
|
title: '唱唱歌!',
|
||||||
tag: '歌回',
|
tag: '歌回',
|
||||||
tagColor: '#61B589',
|
tagColor: '#61B589',
|
||||||
time: '10:00 AM',
|
time: '10:00 AM',
|
||||||
},
|
id: null,
|
||||||
{
|
}],
|
||||||
|
[{
|
||||||
title: '玩点游戏',
|
title: '玩点游戏',
|
||||||
tag: '游戏',
|
tag: '游戏',
|
||||||
tagColor: '#A36565',
|
tagColor: '#A36565',
|
||||||
time: '20:00 PM',
|
time: '20:00 PM',
|
||||||
},
|
id: null,
|
||||||
{
|
}],
|
||||||
|
[{
|
||||||
title: 'Title 3',
|
title: 'Title 3',
|
||||||
tag: 'Tag 3',
|
tag: 'Tag 3',
|
||||||
tagColor: '#7BCDEF',
|
tagColor: '#7BCDEF',
|
||||||
time: '11:00 PM',
|
time: '11:00 PM',
|
||||||
},
|
id: null,
|
||||||
{
|
}],
|
||||||
|
[{
|
||||||
title: null,
|
title: null,
|
||||||
tag: null,
|
tag: null,
|
||||||
tagColor: null,
|
tagColor: null,
|
||||||
time: null,
|
time: null,
|
||||||
},
|
id: null,
|
||||||
{
|
}],
|
||||||
|
[{
|
||||||
title: null,
|
title: null,
|
||||||
tag: null,
|
tag: null,
|
||||||
tagColor: null,
|
tagColor: null,
|
||||||
time: null,
|
time: null,
|
||||||
},
|
id: null,
|
||||||
{
|
}],
|
||||||
|
[{
|
||||||
title: null,
|
title: null,
|
||||||
tag: null,
|
tag: null,
|
||||||
tagColor: null,
|
tagColor: null,
|
||||||
time: null,
|
time: null,
|
||||||
},
|
id: null,
|
||||||
{
|
}],
|
||||||
|
[{
|
||||||
title: null,
|
title: null,
|
||||||
tag: null,
|
tag: null,
|
||||||
tagColor: null,
|
tagColor: null,
|
||||||
time: null,
|
time: null,
|
||||||
},
|
id: null,
|
||||||
|
}],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] as ScheduleWeekInfo[],
|
] as ScheduleWeekInfo[],
|
||||||
|
|||||||
@@ -162,16 +162,20 @@ const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|||||||
// Formatted schedule data for display
|
// Formatted schedule data for display
|
||||||
const formattedSchedule = computed(() => {
|
const formattedSchedule = computed(() => {
|
||||||
if (!currentWeekData.value || !Array.isArray(currentWeekData.value.days)) return [];
|
if (!currentWeekData.value || !Array.isArray(currentWeekData.value.days)) return [];
|
||||||
const scheduleMap = new Map<string, ScheduleDayInfo>();
|
|
||||||
currentWeekData.value.days.forEach((day: ScheduleDayInfo, index: number) => {
|
return daysOfWeek.map((dayKey, index) => {
|
||||||
const dayKey = daysOfWeek[index] || `day${index}`;
|
const dayList = currentWeekData.value!.days[index];
|
||||||
scheduleMap.set(dayKey, day);
|
// 如果当天有多个行程,取第一个展示;如果没有则显示默认
|
||||||
|
const firstItem = Array.isArray(dayList) && dayList.length > 0
|
||||||
|
? dayList[0]
|
||||||
|
: { time: '', tag: '', title: '', tagColor: '', id: null };
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: dayKey,
|
||||||
|
label: dayMap[dayKey] || dayKey,
|
||||||
|
data: firstItem
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return daysOfWeek.map(dayKey => ({
|
|
||||||
key: dayKey,
|
|
||||||
label: dayMap[dayKey] || dayKey,
|
|
||||||
data: scheduleMap.get(dayKey) || { time: '', tag: '', title: '' }
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 方法 ---
|
// --- 方法 ---
|
||||||
|
|||||||
@@ -21,6 +21,19 @@ const currentWeek = computed(() => {
|
|||||||
return isTodayInWeek(item.year, item.week)
|
return isTodayInWeek(item.year, item.week)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const formattedDays = computed(() => {
|
||||||
|
const weekData = currentWeek.value
|
||||||
|
if (!weekData || !Array.isArray(weekData.days)) return []
|
||||||
|
|
||||||
|
return weekData.days.map((dayList) => {
|
||||||
|
// 取每天第一个行程展示
|
||||||
|
if (Array.isArray(dayList) && dayList.length > 0) {
|
||||||
|
return dayList[0]
|
||||||
|
}
|
||||||
|
return { time: '', tag: '', title: '', tagColor: '', id: null }
|
||||||
|
})
|
||||||
|
})
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
return props.data?.map((item) => {
|
return props.data?.map((item) => {
|
||||||
return {
|
return {
|
||||||
@@ -56,7 +69,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
<SaveCompoent
|
<SaveCompoent
|
||||||
:compoent="table"
|
:compoent="table"
|
||||||
:file-name="`周表_${selectedDate}_${userInfo?.name}`"
|
:file-name="`周表_${selectedDate}_${props.userInfo?.name}`"
|
||||||
/>
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
@@ -66,7 +79,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<div class="schedule-template pinky day-container">
|
<div class="schedule-template pinky day-container">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in currentWeek?.days"
|
v-for="(item, index) in formattedDays"
|
||||||
:id="index.toString()"
|
:id="index.toString()"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="schedule-template pinky day-item"
|
class="schedule-template pinky day-item"
|
||||||
@@ -76,15 +89,15 @@ onMounted(() => {
|
|||||||
{{ days[index] }}
|
{{ days[index] }}
|
||||||
</span>
|
</span>
|
||||||
<span class="schedule-template pinky time">
|
<span class="schedule-template pinky time">
|
||||||
{{ item.time }}
|
{{ item?.time }}
|
||||||
</span>
|
</span>
|
||||||
<span class="schedule-template pinky tag">
|
<span class="schedule-template pinky tag">
|
||||||
{{ item.tag }}
|
{{ item?.tag }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="schedule-template pinky day-content-container">
|
<div class="schedule-template pinky day-content-container">
|
||||||
<span
|
<span
|
||||||
v-if="item.tag"
|
v-if="item?.tag"
|
||||||
id="work"
|
id="work"
|
||||||
class="schedule-template pinky day-content"
|
class="schedule-template pinky day-content"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "ESNext",
|
||||||
"module": "esnext",
|
"module": "ESNext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "Bundler",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
@@ -16,7 +16,9 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
},
|
||||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
"lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"],
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
@@ -27,7 +29,9 @@
|
|||||||
"env.d.ts",
|
"env.d.ts",
|
||||||
"default.d.ts",
|
"default.d.ts",
|
||||||
"src/data/chat/ChatClientDirectOpenLive.js",
|
"src/data/chat/ChatClientDirectOpenLive.js",
|
||||||
"src/data/chat/models.js", "src/store/useDanmakuClient.ts",
|
"src/data/chat/models.js",
|
||||||
|
"src/store/useDanmakuClient.ts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|||||||
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["vite.config.mts", "eslint.config.mjs"]
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
|||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
import Markdown from 'unplugin-vue-markdown/vite';
|
import Markdown from 'unplugin-vue-markdown/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import oxlintPlugin from 'vite-plugin-oxlint';
|
|
||||||
import svgLoader from 'vite-svg-loader';
|
import svgLoader from 'vite-svg-loader';
|
||||||
import { VineVitePlugin } from 'vue-vine/vite';
|
import { VineVitePlugin } from 'vue-vine/vite';
|
||||||
|
|
||||||
@@ -18,11 +17,11 @@ const removeSodipodiInkscape = {
|
|||||||
fn: () => {
|
fn: () => {
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
enter: (node, parentNode) => {
|
enter: (node: any, parentNode: any) => {
|
||||||
// 检查元素名称是否以sodipodi:或inkscape:开头
|
// 检查元素名称是否以sodipodi:或inkscape:开头
|
||||||
if (node.name && (node.name.startsWith('sodipodi:') || node.name.startsWith('inkscape:'))) {
|
if (node.name && (node.name.startsWith('sodipodi:') || node.name.startsWith('inkscape:'))) {
|
||||||
// 从父节点的children数组中过滤掉当前节点
|
// 从父节点的children数组中过滤掉当前节点
|
||||||
parentNode.children = parentNode.children.filter(child => child !== node);
|
parentNode.children = parentNode.children.filter((child: any) => child !== node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -85,10 +84,8 @@ export default defineConfig({
|
|||||||
resolvers: [NaiveUiResolver()],
|
resolvers: [NaiveUiResolver()],
|
||||||
dts: 'src/components.d.ts',
|
dts: 'src/components.d.ts',
|
||||||
extensions: ['vue', 'md'],
|
extensions: ['vue', 'md'],
|
||||||
|
|
||||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.vine$/],
|
include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.vine$/],
|
||||||
}),
|
}),
|
||||||
oxlintPlugin(),
|
|
||||||
VineVitePlugin(),
|
VineVitePlugin(),
|
||||||
],
|
],
|
||||||
server: { port: 51000 },
|
server: { port: 51000 },
|
||||||
@@ -103,5 +100,16 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
target: 'esnext',
|
||||||
|
minify: 'esbuild',
|
||||||
|
chunkSizeWarningLimit: 1000,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vue-vendor': ['vue', 'vue-router', 'pinia'],
|
||||||
|
'ui-vendor': ['naive-ui', '@vueuse/core'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user