mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: 二维码
This commit is contained in:
@@ -110,6 +110,8 @@ func SystemConfigToResponse(configs []entity.SystemConfig) *dto.SystemConfigResp
|
||||
response.WechatSearchImage = config.Value
|
||||
case entity.ConfigKeyTelegramQrImage:
|
||||
response.TelegramQrImage = config.Value
|
||||
case entity.ConfigKeyQrCodeStyle:
|
||||
response.QrCodeStyle = config.Value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +267,10 @@ func RequestToSystemConfig(req *dto.SystemConfigRequest) []entity.SystemConfig {
|
||||
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyTelegramQrImage, Value: *req.TelegramQrImage, Type: entity.ConfigTypeString})
|
||||
updatedKeys = append(updatedKeys, entity.ConfigKeyTelegramQrImage)
|
||||
}
|
||||
if req.QrCodeStyle != nil {
|
||||
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyQrCodeStyle, Value: *req.QrCodeStyle, Type: entity.ConfigTypeString})
|
||||
updatedKeys = append(updatedKeys, entity.ConfigKeyQrCodeStyle)
|
||||
}
|
||||
|
||||
// 记录更新的配置项
|
||||
if len(updatedKeys) > 0 {
|
||||
@@ -395,6 +401,8 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte
|
||||
response["wechat_search_image"] = config.Value
|
||||
case entity.ConfigKeyTelegramQrImage:
|
||||
response["telegram_qr_image"] = config.Value
|
||||
case entity.ConfigKeyQrCodeStyle:
|
||||
response["qr_code_style"] = config.Value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,5 +448,6 @@ func getDefaultConfigResponse() *dto.SystemConfigResponse {
|
||||
EnableFloatButtons: false,
|
||||
WechatSearchImage: entity.ConfigDefaultWechatSearchImage,
|
||||
TelegramQrImage: entity.ConfigDefaultTelegramQrImage,
|
||||
QrCodeStyle: entity.ConfigDefaultQrCodeStyle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ type SystemConfigRequest struct {
|
||||
EnableFloatButtons *bool `json:"enable_float_buttons,omitempty"`
|
||||
WechatSearchImage *string `json:"wechat_search_image,omitempty"`
|
||||
TelegramQrImage *string `json:"telegram_qr_image,omitempty"`
|
||||
QrCodeStyle *string `json:"qr_code_style,omitempty"`
|
||||
}
|
||||
|
||||
// SystemConfigResponse 系统配置响应
|
||||
@@ -104,6 +105,7 @@ type SystemConfigResponse struct {
|
||||
EnableFloatButtons bool `json:"enable_float_buttons"`
|
||||
WechatSearchImage string `json:"wechat_search_image"`
|
||||
TelegramQrImage string `json:"telegram_qr_image"`
|
||||
QrCodeStyle string `json:"qr_code_style"`
|
||||
}
|
||||
|
||||
// SystemConfigItem 单个配置项
|
||||
|
||||
@@ -63,6 +63,7 @@ const (
|
||||
ConfigKeyEnableFloatButtons = "enable_float_buttons"
|
||||
ConfigKeyWechatSearchImage = "wechat_search_image"
|
||||
ConfigKeyTelegramQrImage = "telegram_qr_image"
|
||||
ConfigKeyQrCodeStyle = "qr_code_style"
|
||||
)
|
||||
|
||||
// ConfigType 配置类型常量
|
||||
@@ -133,6 +134,14 @@ const (
|
||||
ConfigResponseFieldTelegramProxyPort = "telegram_proxy_port"
|
||||
ConfigResponseFieldTelegramProxyUsername = "telegram_proxy_username"
|
||||
ConfigResponseFieldTelegramProxyPassword = "telegram_proxy_password"
|
||||
|
||||
// 界面配置字段
|
||||
ConfigResponseFieldEnableAnnouncements = "enable_announcements"
|
||||
ConfigResponseFieldAnnouncements = "announcements"
|
||||
ConfigResponseFieldEnableFloatButtons = "enable_float_buttons"
|
||||
ConfigResponseFieldWechatSearchImage = "wechat_search_image"
|
||||
ConfigResponseFieldTelegramQrImage = "telegram_qr_image"
|
||||
ConfigResponseFieldQrCodeStyle = "qr_code_style"
|
||||
)
|
||||
|
||||
// ConfigDefaultValue 配置默认值常量
|
||||
@@ -197,4 +206,5 @@ const (
|
||||
ConfigDefaultEnableFloatButtons = "false"
|
||||
ConfigDefaultWechatSearchImage = ""
|
||||
ConfigDefaultTelegramQrImage = ""
|
||||
ConfigDefaultQrCodeStyle = "Plain"
|
||||
)
|
||||
|
||||
@@ -138,6 +138,7 @@ func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() ([]entity.SystemConfig
|
||||
{Key: entity.ConfigKeyEnableFloatButtons, Value: entity.ConfigDefaultEnableFloatButtons, Type: entity.ConfigTypeBool},
|
||||
{Key: entity.ConfigKeyWechatSearchImage, Value: entity.ConfigDefaultWechatSearchImage, Type: entity.ConfigTypeString},
|
||||
{Key: entity.ConfigKeyTelegramQrImage, Value: entity.ConfigDefaultTelegramQrImage, Type: entity.ConfigTypeString},
|
||||
{Key: entity.ConfigKeyQrCodeStyle, Value: entity.ConfigDefaultQrCodeStyle, Type: entity.ConfigTypeString},
|
||||
}
|
||||
|
||||
err = r.UpsertConfigs(defaultConfigs)
|
||||
|
||||
@@ -8,6 +8,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 二维码动态动画 */
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rainbowMove {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes neonPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 20px rgba(0, 255, 136, 0.5), 0 0 40px rgba(0, 255, 136, 0.3), 0 0 60px rgba(0, 255, 136, 0.1);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 30px rgba(0, 255, 136, 0.8), 0 0 50px rgba(0, 255, 136, 0.5), 0 0 70px rgba(0, 255, 136, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* 动态预设的动画类 */
|
||||
.qr-dynamic {
|
||||
animation: gradientShift 3s ease infinite;
|
||||
}
|
||||
|
||||
.qr-rainbow {
|
||||
animation: rainbowMove 4s linear infinite;
|
||||
}
|
||||
|
||||
.qr-neon {
|
||||
animation: neonPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200;
|
||||
|
||||
285
web/components/Admin/QRCodeStyleSelector.vue
Normal file
285
web/components/Admin/QRCodeStyleSelector.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<n-modal
|
||||
:show="show"
|
||||
:mask-closable="true"
|
||||
preset="card"
|
||||
title="选择二维码样式"
|
||||
style="width: 90%; max-width: 900px;"
|
||||
@close="handleClose"
|
||||
@update:show="handleShowUpdate"
|
||||
>
|
||||
<div class="qr-style-selector">
|
||||
<!-- 样式选择区域 -->
|
||||
<div class="styles-section">
|
||||
<div class="styles-grid">
|
||||
<div
|
||||
v-for="preset in allQrCodePresets"
|
||||
:key="preset.name"
|
||||
class="style-item"
|
||||
:class="{ active: selectedPreset?.name === preset.name }"
|
||||
@click="selectPreset(preset)"
|
||||
>
|
||||
<div class="qr-preview" :style="preset.style">
|
||||
<div :ref="el => setQRContainer(el, preset.name)" v-if="preset"></div>
|
||||
<!-- 选中状态指示器 -->
|
||||
<div v-if="selectedPreset?.name === preset.name" class="selected-indicator">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="style-name">{{ preset.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<n-button @click="handleClose">取消</n-button>
|
||||
<n-button type="primary" @click="confirmSelection">
|
||||
确认选择
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import QRCodeStyling from 'qr-code-styling'
|
||||
import { allQrCodePresets, type Preset } from '~/components/QRCode/presets'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
currentStyle?: string
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:show': [value: boolean]
|
||||
'select': [preset: Preset]
|
||||
}>()
|
||||
|
||||
// 示例数据
|
||||
const sampleData = ref('https://pan.l9.lc')
|
||||
|
||||
// 当前选中的预设
|
||||
const selectedPreset = ref<Preset | null>(null)
|
||||
|
||||
// QR码实例映射
|
||||
const qrInstances = ref<Map<string, QRCodeStyling>>(new Map())
|
||||
|
||||
// 监听显示状态变化
|
||||
watch(() => props.show, (newShow) => {
|
||||
if (newShow) {
|
||||
// 查找当前样式对应的预设
|
||||
const currentPreset = allQrCodePresets.find(preset => preset.name === props.currentStyle)
|
||||
selectedPreset.value = currentPreset || allQrCodePresets[0] // 默认选择 Plain
|
||||
|
||||
// 延迟渲染QR码,确保DOM已经准备好
|
||||
nextTick(() => {
|
||||
renderAllQRCodes()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 设置QR码容器
|
||||
const setQRContainer = (el: HTMLElement, presetName: string) => {
|
||||
if (el) {
|
||||
// 先清空容器内容,防止重复添加
|
||||
el.innerHTML = ''
|
||||
|
||||
const preset = allQrCodePresets.find(p => p.name === presetName)
|
||||
if (preset) {
|
||||
const qrInstance = new QRCodeStyling({
|
||||
data: sampleData.value,
|
||||
...preset,
|
||||
width: 80,
|
||||
height: 80
|
||||
})
|
||||
qrInstance.append(el)
|
||||
|
||||
// 保存实例引用
|
||||
qrInstances.value.set(presetName, qrInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染所有QR码
|
||||
const renderAllQRCodes = () => {
|
||||
// 这个函数会在 setQRContainer 中被调用
|
||||
// 这里不需要额外操作,因为每个组件都会自己渲染
|
||||
}
|
||||
|
||||
// 选择预设
|
||||
const selectPreset = (preset: Preset) => {
|
||||
selectedPreset.value = preset
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
const confirmSelection = () => {
|
||||
if (selectedPreset.value) {
|
||||
emit('select', selectedPreset.value)
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理显示状态更新
|
||||
const handleShowUpdate = (value: boolean) => {
|
||||
emit('update:show', value)
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
emit('update:show', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.qr-style-selector {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 样式选择区域 */
|
||||
.styles-section h3 {
|
||||
margin-bottom: 20px;
|
||||
color: var(--color-text-1);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.styles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.style-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: var(--color-card-bg);
|
||||
}
|
||||
|
||||
.style-item:hover {
|
||||
border-color: var(--color-primary-soft);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.style-item.active {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
box-shadow: 0 0 0 2px rgba(24, 160, 88, 0.2);
|
||||
}
|
||||
|
||||
.qr-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 选中状态指示器 */
|
||||
.selected-indicator {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.style-name {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.style-item.active .style-name {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
.dark .styles-grid {
|
||||
background: var(--color-dark-bg);
|
||||
}
|
||||
|
||||
.dark .style-item {
|
||||
background: var(--color-dark-card);
|
||||
}
|
||||
|
||||
.dark .style-item:hover {
|
||||
background: var(--color-dark-card-hover);
|
||||
}
|
||||
|
||||
.dark .style-item.active {
|
||||
background: rgba(24, 160, 88, 0.1);
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.qr-style-selector {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.styles-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 15px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.style-item {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.styles-grid::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.styles-grid::-webkit-scrollbar-track {
|
||||
background: var(--color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.styles-grid::-webkit-scrollbar-thumb {
|
||||
background: var(--color-text-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.styles-grid::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,14 @@
|
||||
<i class="fa fa-qrcode" style="color: #27ae60;"></i>
|
||||
<div class="hover-show-con dropdown-menu qrcode-btn" style="width: 150px; height: auto;">
|
||||
<div class="qrcode" data-size="100">
|
||||
<n-qr-code :value="currentUrl" :size="100" :bordered="false" />
|
||||
<QRCodeDisplay
|
||||
v-if="qrCodePreset"
|
||||
:data="currentUrl"
|
||||
:preset="qrCodePreset"
|
||||
:width="100"
|
||||
:height="100"
|
||||
/>
|
||||
<n-qr-code v-else :value="currentUrl" :size="100" :bordered="false" />
|
||||
</div>
|
||||
<div class="mt6 px12 muted-color">扫一扫在手机上体验</div>
|
||||
</div>
|
||||
@@ -44,6 +51,7 @@
|
||||
<script setup lang="ts">
|
||||
// 导入系统配置store
|
||||
import { useSystemConfigStore } from '~/stores/systemConfig'
|
||||
import { QRCodeDisplay, findPresetByName } from '~/components/QRCode'
|
||||
|
||||
// 获取系统配置store
|
||||
const systemConfigStore = useSystemConfigStore()
|
||||
@@ -66,6 +74,12 @@ const telegramQrImage = computed(() => {
|
||||
return systemConfigStore.config?.telegram_qr_image || ''
|
||||
})
|
||||
|
||||
// 计算属性:二维码样式预设
|
||||
const qrCodePreset = computed(() => {
|
||||
const styleName = systemConfigStore.config?.qr_code_style || 'Plain'
|
||||
return findPresetByName(styleName)
|
||||
})
|
||||
|
||||
// 滚动到顶部
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({
|
||||
|
||||
@@ -42,6 +42,7 @@ interface Props {
|
||||
preset?: Preset
|
||||
borderRadius?: string
|
||||
background?: string
|
||||
className?: string
|
||||
customImage?: string
|
||||
customImageOptions?: {
|
||||
margin?: number
|
||||
@@ -75,17 +76,37 @@ let qrCodeInstance: QRCodeStyling | null = null
|
||||
// 计算容器样式
|
||||
const containerStyle = computed(() => {
|
||||
if (props.preset) {
|
||||
return {
|
||||
const style = {
|
||||
borderRadius: props.preset.style.borderRadius || '0px',
|
||||
background: props.preset.style.background || 'transparent',
|
||||
padding: '16px'
|
||||
}
|
||||
|
||||
// 如果预设有className,添加到样式中
|
||||
if (props.preset.style.className) {
|
||||
return {
|
||||
...style,
|
||||
class: props.preset.style.className
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
return {
|
||||
|
||||
const style = {
|
||||
borderRadius: props.borderRadius,
|
||||
background: props.background,
|
||||
padding: '16px'
|
||||
}
|
||||
|
||||
// 如果props有className,添加到样式中
|
||||
if (props.className) {
|
||||
return {
|
||||
...style,
|
||||
class: props.className
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
})
|
||||
|
||||
// 生成配置键,用于缓存
|
||||
|
||||
@@ -117,6 +117,236 @@ export const techPreset: Preset = {
|
||||
style: { borderRadius: '0px', background: '#000000' }
|
||||
}
|
||||
|
||||
// 透明预设
|
||||
export const transparentPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Transparent',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23374151',
|
||||
dotsOptions: { color: '#374151', type: 'dots' },
|
||||
cornersSquareOptions: { color: '#374151', type: 'dot' },
|
||||
cornersDotOptions: { color: '#374151', type: 'dot' },
|
||||
imageOptions: { margin: 8 },
|
||||
style: { borderRadius: '8px', background: 'transparent' }
|
||||
}
|
||||
|
||||
// 渐变预设 - 二维码组成部分使用渐变
|
||||
export const gradientModernPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Gradient Modern',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23667eea',
|
||||
dotsOptions: {
|
||||
type: 'rounded',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#667eea' },
|
||||
{ offset: 0.5, color: '#764ba2' },
|
||||
{ offset: 1, color: '#f093fb' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#f093fb' },
|
||||
{ offset: 1, color: '#f5576c' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'dot',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 90,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#fda085' },
|
||||
{ offset: 1, color: '#f5576c' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '16px',
|
||||
background: '#F8FAFC'
|
||||
}
|
||||
}
|
||||
|
||||
// 彩虹渐变预设 - 二维码组成部分使用彩虹渐变
|
||||
export const rainbowPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Rainbow',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23ff0000',
|
||||
dotsOptions: {
|
||||
type: 'dots',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#ff0000' },
|
||||
{ offset: 0.14, color: '#ff7f00' },
|
||||
{ offset: 0.28, color: '#ffff00' },
|
||||
{ offset: 0.42, color: '#00ff00' },
|
||||
{ offset: 0.57, color: '#0000ff' },
|
||||
{ offset: 0.71, color: '#4b0082' },
|
||||
{ offset: 0.85, color: '#9400d3' },
|
||||
{ offset: 1, color: '#ff0000' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#ffff00' },
|
||||
{ offset: 0.5, color: '#00ff00' },
|
||||
{ offset: 1, color: '#0000ff' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'dot',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 90,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#ff7f00' },
|
||||
{ offset: 0.5, color: '#ff00ff' },
|
||||
{ offset: 1, color: '#00ffff' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '20px',
|
||||
background: '#FEFEFE'
|
||||
}
|
||||
}
|
||||
|
||||
// 动态颜色预设 - 二维码组成部分使用动态渐变
|
||||
export const dynamicPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Dynamic',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23ee7752',
|
||||
dotsOptions: {
|
||||
type: 'rounded',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: -45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#ee7752' },
|
||||
{ offset: 0.33, color: '#e73c7e' },
|
||||
{ offset: 0.66, color: '#23a6d5' },
|
||||
{ offset: 1, color: '#23d5ab' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#23d5ab' },
|
||||
{ offset: 0.5, color: '#ee7752' },
|
||||
{ offset: 1, color: '#e73c7e' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'dot',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#23a6d5' },
|
||||
{ offset: 0.5, color: '#23d5ab' },
|
||||
{ offset: 1, color: '#ee7752' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '12px',
|
||||
background: '#F5F5F5',
|
||||
className: 'qr-dynamic'
|
||||
}
|
||||
}
|
||||
|
||||
// 玻璃态预设
|
||||
export const glassPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Glass',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%231F2937',
|
||||
dotsOptions: { color: '#1F2937', type: 'dots' },
|
||||
cornersSquareOptions: { color: '#1F2937', type: 'dot' },
|
||||
cornersDotOptions: { color: '#1F2937', type: 'dot' },
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '16px',
|
||||
background: 'rgba(255, 255, 255, 0.25)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.18)'
|
||||
}
|
||||
}
|
||||
|
||||
// 霓虹预设 - 二维码组成部分使用霓虹渐变
|
||||
export const neonPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Neon',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%2300FF88',
|
||||
dotsOptions: {
|
||||
type: 'square',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#00FF88' },
|
||||
{ offset: 0.5, color: '#00FFAA' },
|
||||
{ offset: 1, color: '#00FFCC' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'square',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#FF00FF' },
|
||||
{ offset: 0.5, color: '#FF00AA' },
|
||||
{ offset: 1, color: '#FF0088' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'square',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 90,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#00FFFF' },
|
||||
{ offset: 0.5, color: '#00FFEE' },
|
||||
{ offset: 1, color: '#00FFCC' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '8px',
|
||||
background: '#1a1a1a',
|
||||
boxShadow: '0 0 20px rgba(0, 255, 136, 0.3), 0 0 40px rgba(0, 255, 136, 0.2), 0 0 60px rgba(0, 255, 136, 0.1)',
|
||||
className: 'qr-neon'
|
||||
}
|
||||
}
|
||||
|
||||
export const naturePreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Nature',
|
||||
@@ -153,6 +383,150 @@ export const coolPreset: Preset = {
|
||||
style: { borderRadius: '20px', background: '#EFF6FF' }
|
||||
}
|
||||
|
||||
// 新增:金属渐变预设
|
||||
export const metallicPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Metallic',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23FFD700',
|
||||
dotsOptions: {
|
||||
type: 'rounded',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 135,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#C0C0C0' },
|
||||
{ offset: 0.25, color: '#E5E5E5' },
|
||||
{ offset: 0.5, color: '#FFD700' },
|
||||
{ offset: 0.75, color: '#E5E5E5' },
|
||||
{ offset: 1, color: '#C0C0C0' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#FFD700' },
|
||||
{ offset: 0.5, color: '#C0C0C0' },
|
||||
{ offset: 1, color: '#808080' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'dot',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#FFD700' },
|
||||
{ offset: 1, color: '#B8860B' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '16px',
|
||||
background: '#F8F8F8'
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:海洋渐变预设
|
||||
export const oceanPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Ocean',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%2300CED1',
|
||||
dotsOptions: {
|
||||
type: 'dots',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#00CED1' },
|
||||
{ offset: 0.5, color: '#4682B4' },
|
||||
{ offset: 1, color: '#191970' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'square',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 90,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#00FFFF' },
|
||||
{ offset: 0.5, color: '#00CED1' },
|
||||
{ offset: 1, color: '#0000CD' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'dot',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#87CEEB' },
|
||||
{ offset: 1, color: '#4682B4' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '20px',
|
||||
background: '#E0F2FE'
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:火焰渐变预设
|
||||
export const firePreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Fire',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23FF4500',
|
||||
dotsOptions: {
|
||||
type: 'classy-rounded',
|
||||
gradient: {
|
||||
type: 'radial',
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#FFFF00' },
|
||||
{ offset: 0.3, color: '#FFA500' },
|
||||
{ offset: 0.7, color: '#FF4500' },
|
||||
{ offset: 1, color: '#8B0000' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 45,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#FF6347' },
|
||||
{ offset: 0.5, color: '#FF4500' },
|
||||
{ offset: 1, color: '#DC143C' }
|
||||
]
|
||||
}
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'square',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
rotation: 90,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#FFA500' },
|
||||
{ offset: 1, color: '#FF4500' }
|
||||
]
|
||||
}
|
||||
},
|
||||
imageOptions: { margin: 8 },
|
||||
style: {
|
||||
borderRadius: '12px',
|
||||
background: '#FFF7ED'
|
||||
}
|
||||
}
|
||||
|
||||
// 原项目预设
|
||||
export const padletPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
@@ -166,17 +540,6 @@ export const padletPreset: Preset = {
|
||||
style: { borderRadius: '24px', background: '#000000' }
|
||||
}
|
||||
|
||||
export const vercelLightPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Vercel Light',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:logo-vercel.svg?color=%23000',
|
||||
dotsOptions: { color: '#000000', type: 'classy' },
|
||||
cornersSquareOptions: { color: '#000000', type: 'square' },
|
||||
cornersDotOptions: { color: '#000000', type: 'square' },
|
||||
imageOptions: { margin: 8 },
|
||||
style: { borderRadius: '0px', background: '#FFFFFF' }
|
||||
}
|
||||
|
||||
export const vercelDarkPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
@@ -190,29 +553,6 @@ export const vercelDarkPreset: Preset = {
|
||||
style: { borderRadius: '0px', background: '#000000' }
|
||||
}
|
||||
|
||||
export const supabaseGreenPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Supabase Green',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/logos:supabase-icon.svg',
|
||||
dotsOptions: { color: '#3ecf8e', type: 'classy-rounded' },
|
||||
cornersSquareOptions: { color: '#3ecf8e', type: 'square' },
|
||||
cornersDotOptions: { color: '#3ecf8e', type: 'square' },
|
||||
imageOptions: { margin: 8 },
|
||||
style: { borderRadius: '12px', background: '#000000' }
|
||||
}
|
||||
|
||||
export const supabasePurplePreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Supabase Purple',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%237700ff',
|
||||
dotsOptions: { color: '#7700ff', type: 'classy-rounded' },
|
||||
cornersSquareOptions: { color: '#7700ff', type: 'square' },
|
||||
cornersDotOptions: { color: '#7700ff', type: 'square' },
|
||||
imageOptions: { margin: 8 },
|
||||
style: { borderRadius: '12px', background: '#000000' }
|
||||
}
|
||||
|
||||
export const uiliciousPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
@@ -250,17 +590,6 @@ export const vueJsPreset: Preset = {
|
||||
style: { borderRadius: '12px', background: '#000000' }
|
||||
}
|
||||
|
||||
export const vuei18nPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
name: 'Vue i18n',
|
||||
data: 'https://pan.l9.lc',
|
||||
image: 'https://api.iconify.design/ion:qr-code-outline.svg?color=%23FF6B6B',
|
||||
dotsOptions: { color: '#FF6B6B', type: 'classy-rounded' },
|
||||
cornersSquareOptions: { color: '#FF6B6B', type: 'square' },
|
||||
cornersDotOptions: { color: '#FF6B6B', type: 'square' },
|
||||
imageOptions: { margin: 8 },
|
||||
style: { borderRadius: '12px', background: '#000000' }
|
||||
}
|
||||
|
||||
export const lyqhtPreset: Preset = {
|
||||
...defaultPresetOptions,
|
||||
@@ -359,19 +688,25 @@ export const builtInPresets: Preset[] = [
|
||||
gradientPreset,
|
||||
minimalPreset,
|
||||
techPreset,
|
||||
// 高级样式预设
|
||||
transparentPreset,
|
||||
gradientModernPreset,
|
||||
rainbowPreset,
|
||||
dynamicPreset,
|
||||
glassPreset,
|
||||
neonPreset,
|
||||
naturePreset,
|
||||
warmPreset,
|
||||
coolPreset,
|
||||
metallicPreset,
|
||||
oceanPreset,
|
||||
firePreset,
|
||||
// 原项目预设
|
||||
padletPreset,
|
||||
vercelLightPreset,
|
||||
vercelDarkPreset,
|
||||
supabaseGreenPreset,
|
||||
supabasePurplePreset,
|
||||
uiliciousPreset,
|
||||
viteConf2023Preset,
|
||||
vueJsPreset,
|
||||
vuei18nPreset,
|
||||
lyqhtPreset,
|
||||
pejuangKodePreset,
|
||||
geeksHackingPreset,
|
||||
|
||||
@@ -96,7 +96,14 @@
|
||||
<div v-if="isQuarkLink" class="space-y-4">
|
||||
<div class=" flex justify-center">
|
||||
<div class="flex qr-container items-center justify-center w-full">
|
||||
<QRCodeDisplay :data="save_url || url" :width="size" :height="size" />
|
||||
<QRCodeDisplay
|
||||
v-if="qrCodePreset"
|
||||
:data="save_url || url"
|
||||
:preset="qrCodePreset"
|
||||
:width="size"
|
||||
:height="size"
|
||||
/>
|
||||
<QRCodeDisplay v-else :data="save_url || url" :width="size" :height="size" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
@@ -109,12 +116,19 @@
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">请使用手机扫码操作</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 其他链接:同时显示链接和二维码 -->
|
||||
<div v-else class="space-y-4">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div class="flex qr-container items-center justify-center w-full">
|
||||
<QRCodeDisplay :data="save_url || url" :preset="supabaseGreenPreset" :width="size" :height="size" />
|
||||
<QRCodeDisplay
|
||||
v-if="qrCodePreset"
|
||||
:data="save_url || url"
|
||||
:preset="qrCodePreset"
|
||||
:width="size"
|
||||
:height="size"
|
||||
/>
|
||||
<QRCodeDisplay v-else :data="save_url || url" :width="size" :height="size" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +163,9 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { QRCodeDisplay, supabaseGreenPreset, preloadCommonLogos } from './QRCode'
|
||||
import { QRCodeDisplay, preloadCommonLogos } from './QRCode'
|
||||
import { useSystemConfigStore } from '~/stores/systemConfig'
|
||||
import { findPresetByName } from './QRCode/presets'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@@ -173,10 +189,19 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// 获取系统配置store
|
||||
const systemConfigStore = useSystemConfigStore()
|
||||
|
||||
const size = ref(180)
|
||||
const color = ref('#409eff')
|
||||
const backgroundColor = ref('#F5F5F5')
|
||||
|
||||
// 计算二维码样式预设
|
||||
const qrCodePreset = computed(() => {
|
||||
const styleName = systemConfigStore.config?.qr_code_style || 'Plain'
|
||||
return findPresetByName(styleName)
|
||||
})
|
||||
|
||||
// 检测是否为移动设备
|
||||
const isMobile = ref(false)
|
||||
|
||||
|
||||
@@ -115,6 +115,27 @@
|
||||
placeholder="请输入版权信息"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 二维码样式 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">二维码样式</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">选择前台显示的二维码样式</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<n-button type="primary" @click="openQRStyleSelector">
|
||||
<template #icon>
|
||||
<i class="fas fa-qrcode"></i>
|
||||
</template>
|
||||
{{ configForm.qr_code_style ? '更换样式' : '选择样式' }}
|
||||
</n-button>
|
||||
<div v-if="configForm.qr_code_style" class="text-sm text-gray-600 dark:text-gray-400">
|
||||
当前样式: <span class="font-semibold">{{ configForm.qr_code_style }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</div>
|
||||
@@ -229,6 +250,13 @@
|
||||
title="选择Logo图片"
|
||||
@select="handleLogoSelect"
|
||||
/>
|
||||
|
||||
<!-- QR Code Style Selector Modal -->
|
||||
<QRCodeStyleSelector
|
||||
v-model:show="showQRStyleSelector"
|
||||
:current-style="configForm.qr_code_style"
|
||||
@select="handleQRStyleSelect"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -244,6 +272,7 @@ import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection
|
||||
import AnnouncementConfig from '~/components/Admin/AnnouncementConfig.vue'
|
||||
import FloatButtonsConfig from '~/components/Admin/FloatButtonsConfig.vue'
|
||||
import ImageSelectorModal from '~/components/Admin/ImageSelectorModal.vue'
|
||||
import QRCodeStyleSelector from '~/components/Admin/QRCodeStyleSelector.vue'
|
||||
|
||||
const notification = useNotification()
|
||||
const { getImageUrl } = useImageUrl()
|
||||
@@ -258,6 +287,9 @@ const showLogoSelector = ref(false)
|
||||
const showWechatSelector = ref(false)
|
||||
const showTelegramSelector = ref(false)
|
||||
|
||||
// QR样式选择器相关数据
|
||||
const showQRStyleSelector = ref(false)
|
||||
|
||||
// 公告类型接口
|
||||
interface Announcement {
|
||||
content: string
|
||||
@@ -279,6 +311,7 @@ interface SiteConfigForm {
|
||||
enable_float_buttons: boolean
|
||||
wechat_search_image: string
|
||||
telegram_qr_image: string
|
||||
qr_code_style: string
|
||||
}
|
||||
|
||||
// 公告配置子组件数据
|
||||
@@ -341,7 +374,8 @@ const {
|
||||
announcements: 'announcements',
|
||||
enable_float_buttons: 'enable_float_buttons',
|
||||
wechat_search_image: 'wechat_search_image',
|
||||
telegram_qr_image: 'telegram_qr_image'
|
||||
telegram_qr_image: 'telegram_qr_image',
|
||||
qr_code_style: 'qr_code_style'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -361,7 +395,8 @@ const configForm = ref<SiteConfigForm>({
|
||||
announcements: [],
|
||||
enable_float_buttons: false,
|
||||
wechat_search_image: '',
|
||||
telegram_qr_image: ''
|
||||
telegram_qr_image: '',
|
||||
qr_code_style: 'Plain'
|
||||
})
|
||||
|
||||
|
||||
@@ -401,7 +436,8 @@ const fetchConfig = async () => {
|
||||
announcements: response.announcements ? JSON.parse(response.announcements) : [],
|
||||
enable_float_buttons: response.enable_float_buttons || false,
|
||||
wechat_search_image: response.wechat_search_image || '',
|
||||
telegram_qr_image: response.telegram_qr_image || ''
|
||||
telegram_qr_image: response.telegram_qr_image || '',
|
||||
qr_code_style: response.qr_code_style || 'Plain'
|
||||
}
|
||||
|
||||
// 设置表单数据和原始数据
|
||||
@@ -543,6 +579,22 @@ const handleTelegramImageSelect = (file: any) => {
|
||||
updateCurrentConfig({ ...configForm.value })
|
||||
}
|
||||
|
||||
// QR样式选择器方法
|
||||
const openQRStyleSelector = () => {
|
||||
showQRStyleSelector.value = true
|
||||
}
|
||||
|
||||
// QR样式选择处理
|
||||
const handleQRStyleSelect = (preset: any) => {
|
||||
configForm.value = {
|
||||
...configForm.value,
|
||||
qr_code_style: preset.name
|
||||
}
|
||||
showQRStyleSelector.value = false
|
||||
// 强制触发更新
|
||||
updateCurrentConfig({ ...configForm.value })
|
||||
}
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(() => {
|
||||
fetchConfig()
|
||||
|
||||
@@ -346,7 +346,7 @@ const handleLogoError = (event: Event) => {
|
||||
|
||||
// 获取资源图片URL,如果没有则返回随机默认封面
|
||||
const getResourceImageUrl = (resource: any) => {
|
||||
console.log('Resource data:', resource)
|
||||
// console.log('Resource data:', resource)
|
||||
// 如果资源有图片,使用资源图片(优先检查image_url,其次检查cover)
|
||||
if (resource.image_url) {
|
||||
return getImageUrl(resource.image_url)
|
||||
|
||||
2
web/pnpm-lock.yaml
generated
2
web/pnpm-lock.yaml
generated
@@ -30,7 +30,7 @@ importers:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
naive-ui:
|
||||
specifier: ^2.37.0
|
||||
specifier: ^2.42.0
|
||||
version: 2.42.0(vue@3.5.18(typescript@5.8.3))
|
||||
pinia:
|
||||
specifier: ^2.1.0
|
||||
|
||||
Reference in New Issue
Block a user