feat: 更新依赖和增强动态表单功能

- 在 package.json 中添加 hammerjs 和 tui-image-editor 依赖
- 在 DynamicForm.vue 中引入并实现装饰性图片功能,支持图片上传、删除和属性调整
- 优化颜色处理逻辑,支持 RGBA 格式
- 更新常量和类型定义,增强代码可读性和可维护性
This commit is contained in:
2025-04-29 05:31:00 +08:00
parent 0591d0575d
commit 968c34f57a
17 changed files with 1724 additions and 239 deletions

View File

@@ -241,12 +241,37 @@
<script lang="ts" setup>
import { ref, onMounted, computed, nextTick, onUnmounted, watch } from 'vue';
import { NCard, NGrid, NGridItem, NSpin, NStatistic, NTabPane, NTabs, useMessage, NTag, NIcon, NDivider, NFlex, NSpace } from 'naive-ui';
import * as echarts from 'echarts';
import * as echarts from 'echarts/core';
import { LineChart, BarChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
MarkPointComponent,
MarkLineComponent,
DataZoomComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { QueryGetAPI } from '@/api/query';
import { ANALYZE_API_URL } from '@/data/constants';
import { useThemeVars } from 'naive-ui';
import { TrendingDown, TrendingUp } from '@vicons/ionicons5';
// 注册必要的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
LineChart,
BarChart,
CanvasRenderer,
MarkPointComponent,
MarkLineComponent,
DataZoomComponent
]);
// types.ts
interface ChartItem {
income: number;

View File

@@ -62,8 +62,9 @@ const shareModalVisiable = ref(false) // 分享模态框可见性
const showOBSModal = ref(false) // OBS预览模态框可见性
const replyMessage = ref('') // 回复输入框内容
const addTagName = ref('') // 添加标签输入框内容
const useCNUrl = useStorage('Settings.UseCNUrl', false) // 是否使用国内镜像URL (持久化存储)
const shareCardRef = ref<HTMLElement | null>(null) // 分享卡片DOM引用
const selectedShareTag = ref<string | null>(null) // 分享时选择的标签
const selectedDirectShareTag = ref<string | null>(null) // 主链接区域选择的标签
const ps = ref(20) // 分页大小 (每页条数)
const pn = ref(1) // 当前页码
const savedCardSize = useStorage<{ width: number; height: number }>('Settings.QuestionDisplay.CardSize', { // 问题展示卡片尺寸 (持久化存储)
@@ -85,10 +86,17 @@ const setting = computed({
},
})
// 分享链接 (当前域名)
const shareUrl = computed(() => `${CURRENT_HOST}@${accountInfo.value?.name}/question-box`)
// 分享链接 (国内镜像)
const shareUrlCN = computed(() => `${CN_HOST}@${accountInfo.value?.name}/question-box`)
// 分享链接 (统一 Host, 根据选择的标签附加参数)
const shareUrlWithTag = (tag: string | null) => {
const base = `${CURRENT_HOST}@${accountInfo.value?.name}/question-box`
return tag ? `${base}?tag=${encodeURIComponent(tag)}` : base
}
// 主链接区域显示的链接
const directShareUrl = computed(() => shareUrlWithTag(selectedDirectShareTag.value))
// 分享模态框中的二维码/卡片链接 (也基于selectedShareTag)
const modalShareUrl = computed(() => shareUrlWithTag(selectedShareTag.value))
// 分页后的问题列表 (仅限收到的问题)
const pagedQuestions = computed(() =>
@@ -181,9 +189,9 @@ function saveShareImage() {
// 保存二维码图片
function saveQRCode() {
if (!shareUrl.value || !accountInfo.value?.name) return
if (!modalShareUrl.value || !accountInfo.value?.name) return
// 使用 QR Server API 生成并下载二维码
downloadImage(`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(shareUrl.value)}`, `vtsuru-提问箱二维码-${accountInfo.value.name}.png`)
downloadImage(`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(modalShareUrl.value)}`, `vtsuru-提问箱二维码-${accountInfo.value.name}.png`)
message.success('二维码已开始下载')
}
@@ -362,21 +370,28 @@ watch(() => accountInfo.value?.settings?.questionBox?.saftyLevel, (newLevel) =>
提问页链接
</NDivider>
<NFlex align="center">
<NInputGroup style="max-width: 400px;">
<!-- 主链接区域输入框和复制按钮 -->
<NInputGroup style="flex-grow: 1; max-width: 500px;">
<NInput
:value="`${useCNUrl ? shareUrlCN : shareUrl}`"
:value="directShareUrl"
readonly
/>
<NButton
secondary
@click="copyToClipboard(`${useCNUrl ? shareUrlCN : shareUrl}`)"
@click="copyToClipboard(directShareUrl)"
>
复制
</NButton>
</NInputGroup>
<NCheckbox v-model:checked="useCNUrl">
使用国内镜像(访问更快)
</NCheckbox>
<!-- 主链接区域标签选择器 -->
<NSelect
v-model:value="selectedDirectShareTag"
placeholder="附加话题 (可选)"
filterable
clearable
:options="useQB.tags.filter(t => t.visiable).map((s) => ({ label: s.name, value: s.name }))"
style="min-width: 150px; max-width: 200px;"
/>
</NFlex>
<!-- 审核中提示 -->
@@ -696,7 +711,7 @@ watch(() => accountInfo.value?.settings?.questionBox?.saftyLevel, (newLevel) =>
closable
style="margin-bottom: 10px;"
>
这里存放的是被内容审查机制自动过滤的提问您可以查看删除或将其标记为正常提问标记为正常后提问将移至我收到的列表
这里存放的是被内容审查机制自动过滤的提问您可以查看删除或将其标记为正常提问标记为正常后提问将移至"我收到的"列表
</NAlert>
<NEmpty
v-if="useQB.trashQuestions.length === 0"
@@ -1034,7 +1049,7 @@ watch(() => accountInfo.value?.settings?.questionBox?.saftyLevel, (newLevel) =>
</div>
<div class="share-card-qr">
<QrcodeVue
:value="shareUrl"
:value="modalShareUrl"
level="Q"
:size="90"
background="#FFFFFF"
@@ -1046,31 +1061,32 @@ watch(() => accountInfo.value?.settings?.questionBox?.saftyLevel, (newLevel) =>
</div>
</div>
<NDivider style="margin-top: 20px; margin-bottom: 10px;">
分享链接设置
</NDivider>
<NSpace vertical>
<NSelect
v-model:value="selectedShareTag"
placeholder="选择要附加到链接的话题 (可选)"
filterable
clearable
:options="useQB.tags.filter(t => t.visiable).map((s) => ({ label: s.name, value: s.name }))"
style="width: 100%;"
/>
</NSpace>
<NDivider style="margin-top: 20px; margin-bottom: 10px;">
分享链接
</NDivider>
<NInputGroup>
<NInputGroupLabel> 默认 </NInputGroupLabel>
<NInputGroupLabel> 链接 </NInputGroupLabel>
<NInput
:value="shareUrl"
:value="modalShareUrl"
readonly
/>
<NButton
secondary
@click="copyToClipboard(shareUrl)"
>
复制
</NButton>
</NInputGroup>
<NInputGroup style="margin-top: 5px;">
<NInputGroupLabel> 国内 </NInputGroupLabel>
<NInput
:value="shareUrlCN"
readonly
/>
<NButton
secondary
@click="copyToClipboard(shareUrlCN)"
@click="copyToClipboard(modalShareUrl)"
>
复制
</NButton>

View File

@@ -136,8 +136,6 @@ const showCopyModal = ref(false)
const updateScheduleModel = ref<ScheduleWeekInfo>({} as ScheduleWeekInfo)
const selectedExistTag = ref()
const useCNUrl = useStorage('Settings.UseCNUrl', false)
const selectedDay = ref(0)
const selectedScheduleYear = ref(new Date().getFullYear())
const selectedScheduleWeek = ref(Number(format(Date.now(), 'w')) + 1)
@@ -270,35 +268,51 @@ onMounted(() => {
<template>
<NSpace align="center">
<NAlert :type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Schedule) ? 'success' : 'warning'"
style="max-width: 200px">
<NAlert
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Schedule) ? 'success' : 'warning'"
style="max-width: 200px"
>
启用日程表
<NDivider vertical />
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Schedule)"
@update:value="setFunctionEnable" />
<NSwitch
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Schedule)"
@update:value="setFunctionEnable"
/>
</NAlert>
<NButton type="primary" @click="showAddModal = true">
<NButton
type="primary"
@click="showAddModal = true"
>
添加周程
</NButton>
<NButton @click="$router.push({ name: 'manage-index', query: { tab: 'template', template: 'schedule' } })">
<NButton @click="$router.push({ name: 'manage-index', query: { tab: 'setting', setting: 'template', template: 'schedule' } })">
修改模板
</NButton>
</NSpace>
<NDivider style="margin: 16px 0 16px 0" title-placement="left">
<NDivider
style="margin: 16px 0 16px 0"
title-placement="left"
>
日程表展示页链接
</NDivider>
<NFlex align="center">
<NInputGroup style="max-width: 400px;">
<NInput :value="`${useCNUrl ? CN_HOST : CURRENT_HOST}@${accountInfo.name}/schedule`" readonly />
<NButton secondary @click="copyToClipboard(`${useCNUrl ? CN_HOST : CURRENT_HOST}@${accountInfo.name}/schedule`)">
<NInput
:value="`${CURRENT_HOST}@${accountInfo.name}/schedule`"
readonly
/>
<NButton
secondary
@click="copyToClipboard(`${CURRENT_HOST}@${accountInfo.name}/schedule`)"
>
复制
</NButton>
</NInputGroup>
<NCheckbox v-model:checked="useCNUrl">
使用国内镜像(访问更快)
</NCheckbox>
</NFlex>
<NDivider style="margin: 16px 0 16px 0" title-placement="left">
<NDivider
style="margin: 16px 0 16px 0"
title-placement="left"
>
订阅链接
<NTooltip>
<template #trigger>
@@ -311,42 +325,84 @@ onMounted(() => {
</NDivider>
<NFlex align="center">
<NInputGroup style="max-width: 400px;">
<NInput :value="`${SCHEDULE_API_URL}${accountInfo.id}.ics`" readonly />
<NButton secondary @click="copyToClipboard(`${SCHEDULE_API_URL}${accountInfo.id}.ics`)">
<NInput
:value="`${SCHEDULE_API_URL}${accountInfo.id}.ics`"
readonly
/>
<NButton
secondary
@click="copyToClipboard(`${SCHEDULE_API_URL}${accountInfo.id}.ics`)"
>
复制
</NButton>
</NInputGroup>
</NFlex>
<NDivider />
<NModal v-model:show="showAddModal" style="width: 600px; max-width: 90vw" preset="card" title="添加周程">
<NModal
v-model:show="showAddModal"
style="width: 600px; max-width: 90vw"
preset="card"
title="添加周程"
>
<NSpace vertical>
年份
<NSelect v-model:value="selectedScheduleYear" :options="yearOptions" />
<NSelect
v-model:value="selectedScheduleYear"
:options="yearOptions"
/>
第几周
<NSelect v-model:value="selectedScheduleWeek" :options="weekOptions" />
<NSelect
v-model:value="selectedScheduleWeek"
:options="weekOptions"
/>
</NSpace>
<NDivider />
<NButton :loading="isFetching" @click="addSchedule">
<NButton
:loading="isFetching"
@click="addSchedule"
>
添加
</NButton>
</NModal>
<NModal v-model:show="showCopyModal" style="width: 600px; max-width: 90vw" preset="card" title="复制周程">
<NModal
v-model:show="showCopyModal"
style="width: 600px; max-width: 90vw"
preset="card"
title="复制周程"
>
<NAlert type="info">
复制为
</NAlert>
<NSpace vertical>
年份
<NSelect v-model:value="selectedScheduleYear" :options="yearOptions" />
<NSelect
v-model:value="selectedScheduleYear"
:options="yearOptions"
/>
第几周
<NSelect v-model:value="selectedScheduleWeek" :options="weekOptions" />
<NSelect
v-model:value="selectedScheduleWeek"
:options="weekOptions"
/>
</NSpace>
<NDivider />
<NButton :loading="isFetching" @click="onCopySchedule">
<NButton
:loading="isFetching"
@click="onCopySchedule"
>
复制
</NButton>
</NModal>
<NModal v-model:show="showUpdateModal" style="width: 600px; max-width: 90vw" preset="card" title="编辑周程">
<NSelect v-model:value="selectedDay" :options="dayOptions" />
<NModal
v-model:show="showUpdateModal"
style="width: 600px; max-width: 90vw"
preset="card"
title="编辑周程"
>
<NSelect
v-model:value="selectedDay"
:options="dayOptions"
/>
<NDivider />
<template v-if="updateScheduleModel">
<NSpace vertical>
@@ -355,29 +411,66 @@ onMounted(() => {
<NInputGroupLabel type="primary">
标签
</NInputGroupLabel>
<NInput v-model:value="updateScheduleModel.days[selectedDay].tag" placeholder="标签 | 留空视为无安排"
style="max-width: 300px" maxlength="10" show-count />
<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" />
<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 />
<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()">
<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>
</NSpace>
</template>
</NModal>
<NSpin v-if="isLoading" show />
<ScheduleList v-else :schedules="schedules ?? []" is-self @on-update="onOpenUpdateModal" @on-delete="onDeleteSchedule"
@on-copy="onOpenCopyModal" />
<NSpin
v-if="isLoading"
show
/>
<ScheduleList
v-else
:schedules="schedules ?? []"
is-self
@on-update="onOpenUpdateModal"
@on-delete="onDeleteSchedule"
@on-copy="onOpenCopyModal"
/>
</template>

View File

@@ -58,7 +58,6 @@ const accountInfo = useAccount()
const isLoading = ref(true)
const showModal = ref(false)
const showModalRenderKey = ref(0)
const useCNUrl = useStorage('Settings.UseCNUrl', false)
const onlyResetNameOnAdded = ref(true)
// 歌曲列表数据
@@ -750,19 +749,16 @@ onMounted(async () => {
<NFlex align="center">
<NInputGroup style="max-width: 400px;">
<NInput
:value="`${useCNUrl ? CN_HOST : CURRENT_HOST}@${accountInfo.name}/song-list`"
:value="`${CURRENT_HOST}@${accountInfo.name}/song-list`"
readonly
/>
<NButton
secondary
@click="copyToClipboard(`${useCNUrl ? CN_HOST : CURRENT_HOST}@${accountInfo.name}/song-list`)"
@click="copyToClipboard(`${CURRENT_HOST}@${accountInfo.name}/song-list`)"
>
复制
</NButton>
</NInputGroup>
<NCheckbox v-model:checked="useCNUrl">
使用国内镜像(访问更快)
</NCheckbox>
</NFlex>
<NDivider style="margin: 16px 0 16px 0" />

View File

@@ -55,7 +55,6 @@ const useBiliAuth = useAuthStore()
const formRef = ref()
const isUpdating = ref(false)
const isAllowedPrivacyPolicy = ref(false)
const useCNUrl = useStorage('Settings.UseCNUrl', false)
const showAddGoodsModal = ref(false)
// 路由哈希处理
@@ -401,7 +400,7 @@ onMounted(() => { })
<!-- 礼物展示页链接 -->
<NDivider
style="margin: 16px 0"
style="margin: 0"
title-placement="left"
>
礼物展示页链接
@@ -413,19 +412,16 @@ onMounted(() => { })
>
<NInputGroup style="max-width: 400px;">
<NInput
:value="`${useCNUrl ? CN_HOST : CURRENT_HOST}@${accountInfo.name}/goods`"
:value="`${CURRENT_HOST}@${accountInfo.name}/goods`"
readonly
/>
<NButton
secondary
@click="copyToClipboard(`${useCNUrl ? CN_HOST : CURRENT_HOST}@${accountInfo.name}/goods`)"
@click="copyToClipboard(`${CURRENT_HOST}@${accountInfo.name}/goods`)"
>
复制
</NButton>
</NInputGroup>
<NCheckbox v-model:checked="useCNUrl">
使用国内镜像(访问更快)
</NCheckbox>
</NFlex>
</NFlex>