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 vue from 'eslint-plugin-vue';
|
||||
import ts from 'typescript-eslint';
|
||||
|
||||
// `VueVine()` 返回一个 ESLint flat config
|
||||
import antfu from '@antfu/eslint-config'
|
||||
import VueVine from '@vue-vine/eslint-config'
|
||||
|
||||
export default [
|
||||
export default antfu(
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
// 项目类型: app (默认) 或 lib
|
||||
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: {
|
||||
"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(),
|
||||
...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",
|
||||
"@hyperdx/browser": "^0.21.2",
|
||||
"@hyperdx/cli": "^0.1.0",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
|
||||
"@microsoft/signalr": "^9.0.6",
|
||||
"@microsoft/signalr-protocol-msgpack": "^9.0.6",
|
||||
"@mixer/postmessage-rpc": "^1.1.4",
|
||||
"@oneidentity/zstd-js": "^1.0.3",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-autostart": "^2.3.0",
|
||||
"@tauri-apps/plugin-http": "^2.4.4",
|
||||
"@tauri-apps/plugin-log": "^2.4.0",
|
||||
"@tauri-apps/plugin-notification": "^2.2.2",
|
||||
"@tauri-apps/plugin-opener": "^2.2.7",
|
||||
"@tauri-apps/plugin-os": "^2.2.1",
|
||||
"@tauri-apps/plugin-process": "^2.2.1",
|
||||
"@tauri-apps/plugin-store": "^2.2.0",
|
||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
||||
"@tauri-apps/api": "^2.8.0",
|
||||
"@tauri-apps/plugin-autostart": "^2.5.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.2",
|
||||
"@tauri-apps/plugin-log": "^2.7.0",
|
||||
"@tauri-apps/plugin-notification": "^2.3.1",
|
||||
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.1",
|
||||
"@tauri-apps/plugin-process": "^2.3.0",
|
||||
"@tauri-apps/plugin-store": "^2.4.0",
|
||||
"@tauri-apps/plugin-updater": "^2.9.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/md5": "^2.3.5",
|
||||
"@types/vue-cropperjs": "^4.1.6",
|
||||
"@vicons/fluent": "^0.13.0",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@vueuse/core": "^13.3.0",
|
||||
"@vueuse/integrations": "^13.3.0",
|
||||
"@vueuse/router": "^13.3.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"@vueuse/integrations": "^13.9.0",
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"bilibili-live-ws": "^6.3.1",
|
||||
"cropperjs": "^2.0.0",
|
||||
"cropperjs": "^2.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"easy-speech": "^2.4.0",
|
||||
"echarts": "^5.6.0",
|
||||
"eslint-plugin-oxlint": "^0.16.12",
|
||||
"echarts": "^6.0.0",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jszip": "^3.10.1",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"linqts": "^2.0.0",
|
||||
"jszip": "^3.10.1",
|
||||
"linqts": "^3.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"naive-ui": "^2.41.1",
|
||||
"nanoid": "^5.1.5",
|
||||
"monaco-editor": "^0.53.0",
|
||||
"naive-ui": "^2.43.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"peerjs": "^1.5.5",
|
||||
"pinia": "^3.0.3",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"unplugin-auto-import": "^19.3.0",
|
||||
"unplugin-vue-components": "^28.7.0",
|
||||
"unplugin-vue-markdown": "^28.3.1",
|
||||
"uuid": "^11.1.0",
|
||||
"vite": "6.3.4",
|
||||
"vite-plugin-oxlint": "^1.3.3",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"unplugin-vue-markdown": "^29.2.0",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "7.1.7",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "3.5.13",
|
||||
"vue": "3.5.22",
|
||||
"vue-cropperjs": "^5.0.0",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-echarts": "^8.0.0",
|
||||
"vue-request": "^2.0.4",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue-turnstile": "^1.0.11",
|
||||
"vue3-aplayer": "^1.7.3",
|
||||
"vue3-marquee": "^4.2.2",
|
||||
"vueuc": "^0.4.64",
|
||||
"worker-timers": "^8.0.22",
|
||||
"vueuc": "^0.4.65",
|
||||
"worker-timers": "^8.0.25",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.16",
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@types/bun": "^1.2.23",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/jszip": "^3.4.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.2.0",
|
||||
"@vue-vine/eslint-config": "^0.2.20",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-vue": "^10.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"@vue-vine/eslint-config": "^1.1.9",
|
||||
"eslint": "^9.36.0",
|
||||
"stylus": "^0.64.0",
|
||||
"typescript": "^5.9.0-dev.20250614",
|
||||
"vue-vine": "^0.4.4"
|
||||
"typescript": "^5.9.2",
|
||||
"vue-vine": "^1.7.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,13 +413,22 @@ export interface LotteryUserCardInfo {
|
||||
export interface ScheduleWeekInfo {
|
||||
year: number
|
||||
week: number
|
||||
days: ScheduleDayInfo[]
|
||||
days: ScheduleDayInfo[][]
|
||||
}
|
||||
export interface ScheduleDayInfo {
|
||||
title: string | null
|
||||
tag: string | null
|
||||
tagColor: string | null
|
||||
time: string | null
|
||||
id: string | null
|
||||
}
|
||||
|
||||
export interface BatchScheduleRequest {
|
||||
startYear: number
|
||||
startWeek: number
|
||||
count: number
|
||||
dayOfWeek: number
|
||||
schedule: ScheduleDayInfo
|
||||
}
|
||||
export enum ThemeType {
|
||||
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 getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
|
||||
const getDate: typeof import('date-fns')['getDate']
|
||||
const getDay: typeof import('date-fns')['getDay']
|
||||
const getDayOfYear: typeof import('date-fns')['getDayOfYear']
|
||||
@@ -179,6 +180,7 @@ declare global {
|
||||
const isSameWeek: typeof import('date-fns')['isSameWeek']
|
||||
const isSameYear: typeof import('date-fns')['isSameYear']
|
||||
const isSaturday: typeof import('date-fns')['isSaturday']
|
||||
const isShallow: typeof import('vue')['isShallow']
|
||||
const isSunday: typeof import('date-fns')['isSunday']
|
||||
const isThisHour: typeof import('date-fns')['isThisHour']
|
||||
const isThisISOWeek: typeof import('date-fns')['isThisISOWeek']
|
||||
@@ -507,6 +509,7 @@ declare global {
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
@@ -553,6 +556,6 @@ declare global {
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @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')
|
||||
}
|
||||
|
||||
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']
|
||||
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
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']
|
||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||
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']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NText: typeof import('naive-ui')['NText']
|
||||
NTime: typeof import('naive-ui')['NTime']
|
||||
PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default']
|
||||
PointHistoryCard: typeof import('./components/manage/PointHistoryCard.vue')['default']
|
||||
PointOrderCard: typeof import('./components/manage/PointOrderCard.vue')['default']
|
||||
@@ -55,7 +42,6 @@ declare module 'vue' {
|
||||
SongList: typeof import('./components/SongList.vue')['default']
|
||||
SongPlayer: typeof import('./components/SongPlayer.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']
|
||||
UpdateNoteContainer: typeof import('./components/UpdateNoteContainer.vue')['default']
|
||||
UserBasicInfoCard: typeof import('./components/UserBasicInfoCard.vue')['default']
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ScheduleWeekInfo } from '@/api/api-models'
|
||||
import { ScheduleWeekInfo, ScheduleDayInfo } from '@/api/api-models'
|
||||
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 themeVars = useThemeVars()
|
||||
|
||||
const weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
function getDateFromWeek(year: number, week: number, dayOfWeek: number): Date {
|
||||
@@ -24,6 +27,8 @@ const emit = defineEmits<{
|
||||
(e: 'onUpdate', schedule: ScheduleWeekInfo): void
|
||||
(e: 'onDelete', 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>
|
||||
|
||||
@@ -78,67 +83,199 @@ const emit = defineEmits<{
|
||||
</template>
|
||||
<NGrid
|
||||
x-gap="8"
|
||||
y-gap="8"
|
||||
cols="1 1200:7"
|
||||
style="align-items: stretch;"
|
||||
>
|
||||
<NGridItem
|
||||
v-for="(day, index) in item.days"
|
||||
v-for="(daySchedules, index) in item.days"
|
||||
:key="index"
|
||||
style="display: flex;"
|
||||
>
|
||||
<NCard
|
||||
size="small"
|
||||
:style="{ height: '65px', backgroundColor: day.tagColor + '1f' }"
|
||||
content-style="padding: 5px;"
|
||||
header-style="padding: 0px 6px 0px 6px;"
|
||||
:embedded="day?.tag != undefined"
|
||||
>
|
||||
<template #header-extra>
|
||||
<template v-if="day.tag">
|
||||
<NSpace :size="5">
|
||||
<NBadge
|
||||
v-if="day.tagColor"
|
||||
dot
|
||||
:color="day.tagColor"
|
||||
/>
|
||||
<NEllipsis>
|
||||
<NText :style="{ color: day.tagColor }">
|
||||
{{ day.tag }}
|
||||
</NText>
|
||||
</NEllipsis>
|
||||
</NSpace>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NText
|
||||
depth="3"
|
||||
style="font-size: 11px"
|
||||
italic
|
||||
>
|
||||
休息
|
||||
</NText>
|
||||
</template>
|
||||
</template>
|
||||
<template #header>
|
||||
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
|
||||
<div
|
||||
:style="{
|
||||
marginBottom: '6px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '600',
|
||||
background: `linear-gradient(135deg, ${themeVars.primaryColorSuppl}15 0%, ${themeVars.primaryColorSuppl}25 100%)`,
|
||||
borderRadius: '4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
}"
|
||||
>
|
||||
<NTime
|
||||
:time="getDateFromWeek(item.year, item.week, index)"
|
||||
format="MM/dd"
|
||||
:style="{ color: themeVars.primaryColor }"
|
||||
/>
|
||||
<NText>{{ weekdays[index] }}</NText>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex; flex-direction: column; min-height: 65px;">
|
||||
<NCard
|
||||
v-if="daySchedules.length === 0"
|
||||
size="small"
|
||||
:style="{
|
||||
minHeight: '40px',
|
||||
background: `linear-gradient(135deg, ${themeVars.cardColor} 0%, ${themeVars.bodyColor} 100%)`,
|
||||
border: `1px dashed ${themeVars.dividerColor}`,
|
||||
cursor: isSelf ? 'pointer' : 'default',
|
||||
transition: 'all 0.2s ease',
|
||||
}"
|
||||
: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
|
||||
:depth="3"
|
||||
style="font-size: 12px"
|
||||
strong
|
||||
:italic="!day.tag"
|
||||
depth="3"
|
||||
style="font-size: 11px; font-style: italic;"
|
||||
:style="{ opacity: isSelf ? 0.5 : 0.6 }"
|
||||
>
|
||||
<NTime
|
||||
:time="getDateFromWeek(item.year, item.week, index)"
|
||||
format="MM/dd"
|
||||
/>
|
||||
{{ weekdays[index] }}
|
||||
{{ day.time }}
|
||||
休息
|
||||
</NText>
|
||||
</template>
|
||||
<template v-if="day?.title">
|
||||
<NEllipsis>
|
||||
<NText style="font-size: 13px">
|
||||
{{ day.title }}
|
||||
</NText>
|
||||
</NEllipsis>
|
||||
</template>
|
||||
</NCard>
|
||||
</NCard>
|
||||
<NSpace
|
||||
v-else
|
||||
vertical
|
||||
:size="4"
|
||||
>
|
||||
<NCard
|
||||
v-for="(schedule, scheduleIndex) in daySchedules"
|
||||
: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>
|
||||
</NGrid>
|
||||
</NCard>
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
VideoAdd20Filled,
|
||||
Mail24Filled,
|
||||
} 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 {
|
||||
NAlert,
|
||||
@@ -68,6 +68,50 @@ const themeType = useStorage('Settings.Theme', ThemeType.Auto)
|
||||
|
||||
// 收藏功能相关
|
||||
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()
|
||||
@@ -130,259 +174,323 @@ const isBiliVerified = computed(() => accountInfo.value?.isBiliVerified)
|
||||
// 图标渲染函数 - 用于菜单项
|
||||
const renderIcon = (icon: any) => () => h(NIcon, null, { default: () => h(icon) })
|
||||
|
||||
// 菜单配置
|
||||
// 菜单配置(支持分组与收藏置顶)
|
||||
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: () => '历史' }),
|
||||
key: 'manage-history',
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
icon: renderIcon(AnalyticsSharp),
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-live' } }, { default: () => '直播记录' }),
|
||||
key: 'manage-live',
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
icon: renderIcon(Live24Filled),
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-analyze' } }, { default: () => '直播数据' }),
|
||||
key: 'manage-analyze',
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
icon: renderIcon(Eye),
|
||||
},
|
||||
{
|
||||
}),
|
||||
]
|
||||
|
||||
const dataItems = [
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-event' } }, { default: () => '舰长和SC' }),
|
||||
key: 'manage-event',
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
icon: renderIcon(VehicleShip24Filled),
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-point' } }, { default: () => '积分和礼物' }),
|
||||
key: 'manage-point',
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
icon: renderIcon(BookCoins20Filled),
|
||||
},
|
||||
{
|
||||
}),
|
||||
]
|
||||
|
||||
const toolsItems = [
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-schedule' } }, { default: () => '日程' }),
|
||||
key: 'manage-schedule',
|
||||
icon: renderIcon(CalendarClock24Filled),
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-songList' } }, { default: () => '歌单' }),
|
||||
key: 'manage-songList',
|
||||
icon: renderIcon(MusicalNote),
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-questionBox' } }, { default: () => '棉花糖 (提问箱' }),
|
||||
key: 'manage-questionBox',
|
||||
icon: renderIcon(Chatbox),
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-videoCollect' } }, { default: () => '视频征集' }),
|
||||
key: 'manage-videoCollect',
|
||||
icon: renderIcon(VideoAdd20Filled),
|
||||
disabled: accountInfo.value?.isEmailVerified === false,
|
||||
},
|
||||
{
|
||||
}),
|
||||
withFavoriteExtra({
|
||||
label: () => h(RouterLink, { to: { name: 'manage-lottery' } }, { default: () => '动态抽奖' }),
|
||||
key: 'manage-lottery',
|
||||
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
|
||||
class="manage-sider-menu"
|
||||
style="margin-top: 12px"
|
||||
:disabled="accountInfo?.isEmailVerified !== true"
|
||||
:default-value="($route.meta.parent as string) ?? $route.name?.toString()"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:icon-size="16"
|
||||
:root-indent="10"
|
||||
:indent="12"
|
||||
:options="menuOptions"
|
||||
/>
|
||||
|
||||
@@ -1337,4 +1449,40 @@ onMounted(() => {
|
||||
.music-player-card .n-tag:hover {
|
||||
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>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
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 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 { TagQuestionMark16Filled } from '@vicons/fluent'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { addWeeks, endOfWeek, endOfYear, format, isBefore, startOfWeek, startOfYear } from 'date-fns'
|
||||
import {
|
||||
NAlert,
|
||||
NBadge,
|
||||
NButton,
|
||||
NCard,
|
||||
NCheckbox,
|
||||
NColorPicker,
|
||||
NDivider,
|
||||
NFlex,
|
||||
NIcon,
|
||||
NInput,
|
||||
NInputGroup,
|
||||
NInputGroupLabel,
|
||||
@@ -24,12 +25,13 @@ import {
|
||||
NSpace,
|
||||
NSpin,
|
||||
NSwitch,
|
||||
NText,
|
||||
NTimePicker,
|
||||
NTooltip,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
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 = {
|
||||
user: {
|
||||
@@ -76,33 +78,219 @@ const weekOptions = computed(() => {
|
||||
return weeks
|
||||
})
|
||||
const dayOptions = computed(() => {
|
||||
const days = [] as SelectMixedOption[]
|
||||
const days: SelectMixedOption[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
try {
|
||||
days.push({
|
||||
label: updateScheduleModel.value?.days[i].tag ? weekdays[i] + ' (已安排)' : weekdays[i],
|
||||
value: i,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
const entries = updateScheduleModel.value?.days?.[i] ?? []
|
||||
const count = entries.length
|
||||
days.push({
|
||||
label: count > 0 ? `${weekdays[i]} (共${count}项)` : weekdays[i],
|
||||
value: i,
|
||||
})
|
||||
}
|
||||
return days
|
||||
})
|
||||
const existTagOptions = computed(() => {
|
||||
const colors = [] as SelectMixedOption[]
|
||||
const colors: SelectMixedOption[] = []
|
||||
const exists = new Set<string>()
|
||||
schedules.value?.forEach((s) => {
|
||||
s.days.forEach((d) => {
|
||||
if (d.tag && !colors.find((c) => c.value == d.tagColor && c.label == d.tag)) {
|
||||
colors.push({
|
||||
label: d.tag,
|
||||
value: d.tagColor ?? '',
|
||||
})
|
||||
}
|
||||
s.days.forEach((dayList) => {
|
||||
dayList.forEach((item) => {
|
||||
const tag = item.tag ?? ''
|
||||
const color = normalizeColor(item.tagColor) ?? ''
|
||||
if (tag) {
|
||||
const key = `${tag}__${color}`
|
||||
if (!exists.has(key)) {
|
||||
exists.add(key)
|
||||
colors.push({
|
||||
label: tag,
|
||||
value: color,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
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) {
|
||||
const startDate = startOfYear(new Date(year, 0, 1))
|
||||
const endDate = endOfYear(new Date(year, 11, 31))
|
||||
@@ -125,7 +313,7 @@ function getAllWeeks(year: number) {
|
||||
return weeks
|
||||
}
|
||||
const accountInfo = useAccount()
|
||||
const schedules = ref<ScheduleWeekInfo[]>()
|
||||
const schedules = ref<ScheduleWeekInfo[]>([])
|
||||
const message = useMessage()
|
||||
|
||||
const isLoading = ref(true)
|
||||
@@ -133,13 +321,47 @@ const isLoading = ref(true)
|
||||
const showUpdateModal = ref(false)
|
||||
const showAddModal = ref(false)
|
||||
const showCopyModal = ref(false)
|
||||
const updateScheduleModel = ref<ScheduleWeekInfo>({} as ScheduleWeekInfo)
|
||||
const updateScheduleModel = ref<ScheduleWeekInfo>(createEmptyWeek())
|
||||
const selectedExistTag = ref()
|
||||
const editingItemIndex = ref<number | null>(null)
|
||||
|
||||
const selectedDay = ref(0)
|
||||
const selectedScheduleYear = ref(new Date().getFullYear())
|
||||
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() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<ScheduleWeekInfo[]>(SCHEDULE_API_URL + 'get', {
|
||||
@@ -147,12 +369,12 @@ async function get() {
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
schedules.value = data.data
|
||||
schedules.value = (data.data ?? []).map((week) => normalizeWeek(week))
|
||||
} else {
|
||||
message.error('加载失败: ' + data.message)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
message.error('加载失败')
|
||||
})
|
||||
.finally(() => (isLoading.value = false))
|
||||
@@ -160,15 +382,17 @@ async function get() {
|
||||
const isFetching = ref(false)
|
||||
async function addSchedule() {
|
||||
isFetching.value = true
|
||||
const emptyWeek = createEmptyWeek(selectedScheduleYear.value, selectedScheduleWeek.value)
|
||||
await QueryPostAPI(SCHEDULE_API_URL + 'update', {
|
||||
year: selectedScheduleYear.value,
|
||||
week: selectedScheduleWeek.value,
|
||||
year: emptyWeek.year,
|
||||
week: emptyWeek.week,
|
||||
days: emptyWeek.days,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('添加成功')
|
||||
showAddModal.value = false
|
||||
get()
|
||||
schedules.value = [...schedules.value, emptyWeek]
|
||||
} else {
|
||||
message.error('添加失败: ' + data.message)
|
||||
}
|
||||
@@ -183,30 +407,60 @@ async function onCopySchedule() {
|
||||
} else {
|
||||
updateScheduleModel.value.year = selectedScheduleYear.value
|
||||
updateScheduleModel.value.week = selectedScheduleWeek.value
|
||||
await onUpdateSchedule()
|
||||
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||
await saveSchedule(null)
|
||||
showCopyModal.value = false
|
||||
}
|
||||
}
|
||||
async function onUpdateSchedule() {
|
||||
async function saveSchedule(day: number | null) {
|
||||
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,
|
||||
week: updateScheduleModel.value.week,
|
||||
day: selectedDay.value,
|
||||
days: updateScheduleModel.value?.days,
|
||||
})
|
||||
days: sanitizedDays,
|
||||
}
|
||||
|
||||
if (day !== null && day !== undefined) {
|
||||
payload.day = day
|
||||
}
|
||||
|
||||
await QueryPostAPI(SCHEDULE_API_URL + 'update', payload)
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('成功')
|
||||
const s = schedules.value?.find(
|
||||
(s) => s.year == selectedScheduleYear.value && s.week == selectedScheduleWeek.value,
|
||||
const normalizedWeek = normalizeWeek({
|
||||
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 {
|
||||
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 {
|
||||
message.error('修改失败: ' + data.message)
|
||||
}
|
||||
@@ -215,6 +469,9 @@ async function onUpdateSchedule() {
|
||||
isFetching.value = false
|
||||
})
|
||||
}
|
||||
async function onUpdateSchedule() {
|
||||
await saveSchedule(selectedDay.value)
|
||||
}
|
||||
async function onDeleteSchedule(schedule: ScheduleWeekInfo) {
|
||||
await QueryGetAPI(SCHEDULE_API_URL + 'del', {
|
||||
year: schedule.year,
|
||||
@@ -229,19 +486,97 @@ async function onDeleteSchedule(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
|
||||
}
|
||||
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
|
||||
}
|
||||
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) {
|
||||
updateScheduleModel.value.days[selectedDay.value].tagColor = value
|
||||
updateScheduleModel.value.days[selectedDay.value].tag = option.label as string
|
||||
ensureDayInitialized(updateScheduleModel.value, selectedDay.value)
|
||||
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 }) =>
|
||||
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [
|
||||
option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null,
|
||||
@@ -395,7 +730,7 @@ onMounted(() => {
|
||||
</NModal>
|
||||
<NModal
|
||||
v-model:show="showUpdateModal"
|
||||
style="width: 600px; max-width: 90vw"
|
||||
style="width: 800px; max-width: 95vw; max-height: 90vh;"
|
||||
preset="card"
|
||||
title="编辑周程"
|
||||
>
|
||||
@@ -405,60 +740,142 @@ onMounted(() => {
|
||||
/>
|
||||
<NDivider />
|
||||
<template v-if="updateScheduleModel">
|
||||
<NSpace vertical>
|
||||
<NSpace>
|
||||
<NInputGroup>
|
||||
<NInputGroupLabel type="primary">
|
||||
标签
|
||||
</NInputGroupLabel>
|
||||
<NInput
|
||||
v-model:value="updateScheduleModel.days[selectedDay].tag"
|
||||
placeholder="标签 | 留空视为无安排"
|
||||
style="max-width: 300px"
|
||||
maxlength="10"
|
||||
show-count
|
||||
/>
|
||||
</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()"
|
||||
<div
|
||||
style="
|
||||
max-height: calc(90vh - 300px);
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
"
|
||||
>
|
||||
<NSpace
|
||||
vertical
|
||||
:size="12"
|
||||
>
|
||||
保存
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NButton
|
||||
type="primary"
|
||||
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>
|
||||
</NModal>
|
||||
<NSpin
|
||||
@@ -472,5 +889,39 @@ onMounted(() => {
|
||||
@on-update="onOpenUpdateModal"
|
||||
@on-delete="onDeleteSchedule"
|
||||
@on-copy="onOpenCopyModal"
|
||||
@on-edit-item="onEditScheduleItem"
|
||||
@on-delete-item="onDeleteScheduleItem"
|
||||
/>
|
||||
</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,
|
||||
week: 30,
|
||||
days: [
|
||||
{
|
||||
[{
|
||||
title: '唱唱歌!',
|
||||
tag: '歌回',
|
||||
tagColor: '#61B589',
|
||||
time: '10:00 AM',
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
}],
|
||||
[{
|
||||
title: '玩点游戏',
|
||||
tag: '游戏',
|
||||
tagColor: '#A36565',
|
||||
time: '20:00 PM',
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
}],
|
||||
[{
|
||||
title: 'Title 3',
|
||||
tag: 'Tag 3',
|
||||
tagColor: '#7BCDEF',
|
||||
time: '11:00 PM',
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
}],
|
||||
[{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
}],
|
||||
[{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
}],
|
||||
[{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
}],
|
||||
[{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
id: null,
|
||||
}],
|
||||
],
|
||||
},
|
||||
] as ScheduleWeekInfo[],
|
||||
|
||||
@@ -162,16 +162,20 @@ const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
// Formatted schedule data for display
|
||||
const formattedSchedule = computed(() => {
|
||||
if (!currentWeekData.value || !Array.isArray(currentWeekData.value.days)) return [];
|
||||
const scheduleMap = new Map<string, ScheduleDayInfo>();
|
||||
currentWeekData.value.days.forEach((day: ScheduleDayInfo, index: number) => {
|
||||
const dayKey = daysOfWeek[index] || `day${index}`;
|
||||
scheduleMap.set(dayKey, day);
|
||||
|
||||
return daysOfWeek.map((dayKey, index) => {
|
||||
const dayList = currentWeekData.value!.days[index];
|
||||
// 如果当天有多个行程,取第一个展示;如果没有则显示默认
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
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(() => {
|
||||
return props.data?.map((item) => {
|
||||
return {
|
||||
@@ -56,7 +69,7 @@ onMounted(() => {
|
||||
/>
|
||||
<SaveCompoent
|
||||
:compoent="table"
|
||||
:file-name="`周表_${selectedDate}_${userInfo?.name}`"
|
||||
:file-name="`周表_${selectedDate}_${props.userInfo?.name}`"
|
||||
/>
|
||||
</NSpace>
|
||||
<NDivider />
|
||||
@@ -66,7 +79,7 @@ onMounted(() => {
|
||||
>
|
||||
<div class="schedule-template pinky day-container">
|
||||
<div
|
||||
v-for="(item, index) in currentWeek?.days"
|
||||
v-for="(item, index) in formattedDays"
|
||||
:id="index.toString()"
|
||||
:key="index"
|
||||
class="schedule-template pinky day-item"
|
||||
@@ -76,15 +89,15 @@ onMounted(() => {
|
||||
{{ days[index] }}
|
||||
</span>
|
||||
<span class="schedule-template pinky time">
|
||||
{{ item.time }}
|
||||
{{ item?.time }}
|
||||
</span>
|
||||
<span class="schedule-template pinky tag">
|
||||
{{ item.tag }}
|
||||
{{ item?.tag }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="schedule-template pinky day-content-container">
|
||||
<span
|
||||
v-if="item.tag"
|
||||
v-if="item?.tag"
|
||||
id="work"
|
||||
class="schedule-template pinky day-content"
|
||||
>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "Bundler",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
@@ -16,7 +16,9 @@
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"],
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
@@ -27,7 +29,9 @@
|
||||
"env.d.ts",
|
||||
"default.d.ts",
|
||||
"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 Markdown from 'unplugin-vue-markdown/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import oxlintPlugin from 'vite-plugin-oxlint';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
import { VineVitePlugin } from 'vue-vine/vite';
|
||||
|
||||
@@ -18,11 +17,11 @@ const removeSodipodiInkscape = {
|
||||
fn: () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
enter: (node: any, parentNode: any) => {
|
||||
// 检查元素名称是否以sodipodi:或inkscape:开头
|
||||
if (node.name && (node.name.startsWith('sodipodi:') || node.name.startsWith('inkscape:'))) {
|
||||
// 从父节点的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()],
|
||||
dts: 'src/components.d.ts',
|
||||
extensions: ['vue', 'md'],
|
||||
|
||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.vine$/],
|
||||
}),
|
||||
oxlintPlugin(),
|
||||
VineVitePlugin(),
|
||||
],
|
||||
server: { port: 51000 },
|
||||
@@ -103,5 +100,16 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
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