mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
Compare commits
2 Commits
3582cbcf64
...
0f16cb3241
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f16cb3241 | |||
| 46ff024549 |
@@ -12,7 +12,7 @@
|
|||||||
<meta name="description" content="为主播提供便利功能" />
|
<meta name="description" content="为主播提供便利功能" />
|
||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
src="https://umami.vtsuru.live/script.js"
|
src="https://analytics.suki.club/script.js"
|
||||||
data-website-id="05567214-d234-4076-9228-e4d69e3d202f"
|
data-website-id="05567214-d234-4076-9228-e4d69e3d202f"
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import DataManager from './components/autoaction/DataManager.vue'
|
|||||||
import CheckInSettings from './components/autoaction/settings/CheckInSettings.vue'
|
import CheckInSettings from './components/autoaction/settings/CheckInSettings.vue'
|
||||||
import GlobalScheduledSettings from './components/autoaction/settings/GlobalScheduledSettings.vue'
|
import GlobalScheduledSettings from './components/autoaction/settings/GlobalScheduledSettings.vue'
|
||||||
import TimerCountdown from './components/autoaction/TimerCountdown.vue'
|
import TimerCountdown from './components/autoaction/TimerCountdown.vue'
|
||||||
|
import BiliUserSelector from '@/components/common/BiliUserSelector.vue'
|
||||||
|
|
||||||
const autoActionStore = useAutoAction()
|
const autoActionStore = useAutoAction()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -66,7 +67,7 @@ const editingActionId = ref<string | null>(null)
|
|||||||
const showSetNextModal = ref(false)
|
const showSetNextModal = ref(false)
|
||||||
const targetNextActionId = ref<string | null>(null)
|
const targetNextActionId = ref<string | null>(null)
|
||||||
const showTestModal = ref(false)
|
const showTestModal = ref(false)
|
||||||
const testUid = ref<string>('10004')
|
const testUid = ref<number | undefined>(10004)
|
||||||
const currentTestType = ref<TriggerType | null>(null)
|
const currentTestType = ref<TriggerType | null>(null)
|
||||||
|
|
||||||
const triggerTypeOptions = [
|
const triggerTypeOptions = [
|
||||||
@@ -425,7 +426,7 @@ function handleTestClick(type: TriggerType) {
|
|||||||
if (type === TriggerType.GUARD) {
|
if (type === TriggerType.GUARD) {
|
||||||
// 为舰长相关(私信)测试显示UID输入对话框
|
// 为舰长相关(私信)测试显示UID输入对话框
|
||||||
currentTestType.value = type
|
currentTestType.value = type
|
||||||
testUid.value = '10004' // 默认值
|
testUid.value = 10004 // 默认值
|
||||||
showTestModal.value = true
|
showTestModal.value = true
|
||||||
} else {
|
} else {
|
||||||
// 其他类型直接测试
|
// 其他类型直接测试
|
||||||
@@ -441,8 +442,8 @@ function confirmTest() {
|
|||||||
showTestModal.value = false
|
showTestModal.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const uid = Number.parseInt(testUid.value)
|
const uid = Number(testUid.value)
|
||||||
if (isNaN(uid) || uid <= 0) {
|
if (!Number.isFinite(uid) || uid <= 0) {
|
||||||
message.error('请输入有效的UID')
|
message.error('请输入有效的UID')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -818,10 +819,9 @@ function confirmTest() {
|
|||||||
>
|
>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<div>请输入私信接收者的UID:</div>
|
<div>请输入私信接收者的UID:</div>
|
||||||
<NInput
|
<BiliUserSelector
|
||||||
v-model:value="testUid"
|
v-model:value="testUid"
|
||||||
placeholder="请输入UID"
|
placeholder="请输入B站用户UID"
|
||||||
type="text"
|
|
||||||
/>
|
/>
|
||||||
<NText
|
<NText
|
||||||
type="info"
|
type="info"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useAutoAction } from '@/client/store/useAutoAction'
|
|||||||
import { CHECKIN_API_URL } from '@/data/constants'
|
import { CHECKIN_API_URL } from '@/data/constants'
|
||||||
import AutoActionEditor from '../AutoActionEditor.vue'
|
import AutoActionEditor from '../AutoActionEditor.vue'
|
||||||
import TemplateHelper from '../TemplateHelper.vue'
|
import TemplateHelper from '../TemplateHelper.vue'
|
||||||
|
import BiliUserSelector from '@/components/common/BiliUserSelector.vue'
|
||||||
|
|
||||||
interface LiveInfo {
|
interface LiveInfo {
|
||||||
roomId?: number
|
roomId?: number
|
||||||
@@ -775,11 +776,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NForm :label-width="100">
|
<NForm :label-width="100">
|
||||||
<NFormItem label="用户UID">
|
<NFormItem label="用户UID">
|
||||||
<NInputNumber
|
<BiliUserSelector
|
||||||
v-model:value="testUid"
|
v-model:value="testUid"
|
||||||
:min="1"
|
placeholder="请输入B站用户UID"
|
||||||
style="width: 100%"
|
@user-info-loaded="(u) => { if (u?.name && (!testUsername || testUsername === '测试用户')) testUsername = u.name }"
|
||||||
placeholder="输入用户数字ID"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="用户名">
|
<NFormItem label="用户名">
|
||||||
|
|||||||
6
src/components.d.ts
vendored
6
src/components.d.ts
vendored
@@ -9,6 +9,7 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AddressDisplay: typeof import('./components/manage/AddressDisplay.vue')['default']
|
AddressDisplay: typeof import('./components/manage/AddressDisplay.vue')['default']
|
||||||
|
BiliUserSelector: typeof import('./components/common/BiliUserSelector.vue')['default']
|
||||||
DanmakuContainer: typeof import('./components/DanmakuContainer.vue')['default']
|
DanmakuContainer: typeof import('./components/DanmakuContainer.vue')['default']
|
||||||
DanmakuItem: typeof import('./components/DanmakuItem.vue')['default']
|
DanmakuItem: typeof import('./components/DanmakuItem.vue')['default']
|
||||||
DynamicForm: typeof import('./components/DynamicForm.vue')['default']
|
DynamicForm: typeof import('./components/DynamicForm.vue')['default']
|
||||||
@@ -18,13 +19,18 @@ declare module 'vue' {
|
|||||||
LabelItem: typeof import('./components/LabelItem.vue')['default']
|
LabelItem: typeof import('./components/LabelItem.vue')['default']
|
||||||
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
||||||
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
||||||
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NCard: typeof import('naive-ui')['NCard']
|
NCard: typeof import('naive-ui')['NCard']
|
||||||
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
NFlex: typeof import('naive-ui')['NFlex']
|
NFlex: typeof import('naive-ui')['NFlex']
|
||||||
|
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||||
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
NText: typeof import('naive-ui')['NText']
|
NText: typeof import('naive-ui')['NText']
|
||||||
|
|||||||
188
src/components/common/BiliUserSelector.vue
Normal file
188
src/components/common/BiliUserSelector.vue
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NAutoComplete, NAvatar, NFlex, NText } from 'naive-ui'
|
||||||
|
import type { AutoCompleteOption } from 'naive-ui'
|
||||||
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
|
import { computed, h, ref, watch } from 'vue'
|
||||||
|
import { VTSURU_API_URL } from '@/data/constants'
|
||||||
|
|
||||||
|
interface BiliUserInfo {
|
||||||
|
mid: number
|
||||||
|
name: string
|
||||||
|
face: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BiliApiResponse {
|
||||||
|
code: number
|
||||||
|
data?: {
|
||||||
|
card?: BiliUserInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BiliUserSelectorOption = AutoCompleteOption & { userInfo?: BiliUserInfo }
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
placeholder?: string
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
disabled?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'userInfoLoaded': [userInfo: BiliUserInfo | null]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 使用 defineModel 作为外部 v-model:value 绑定
|
||||||
|
const model = defineModel<number | undefined>('value')
|
||||||
|
|
||||||
|
const inputValue = ref('')
|
||||||
|
const options = ref<BiliUserSelectorOption[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const selectedUserInfo = ref<BiliUserInfo | null>(null)
|
||||||
|
|
||||||
|
// 监听外部 v-model:value 变化,当外部设置了值时加载用户信息
|
||||||
|
watch(
|
||||||
|
() => model.value,
|
||||||
|
async (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
inputValue.value = String(newValue)
|
||||||
|
if (!selectedUserInfo.value || selectedUserInfo.value.mid !== newValue) {
|
||||||
|
await loadUserInfo(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inputValue.value = ''
|
||||||
|
selectedUserInfo.value = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// 加载用户信息
|
||||||
|
async function loadUserInfo(uid: number) {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const response = await fetch(`${VTSURU_API_URL}bili-user-info/${uid}`)
|
||||||
|
const data: BiliApiResponse = await response.json()
|
||||||
|
|
||||||
|
if (data.code === 0 && data.data?.card) {
|
||||||
|
const userInfo = data.data.card
|
||||||
|
selectedUserInfo.value = userInfo
|
||||||
|
|
||||||
|
options.value = [{
|
||||||
|
label: `${userInfo.name} (${userInfo.mid})`,
|
||||||
|
value: String(userInfo.mid),
|
||||||
|
userInfo,
|
||||||
|
}] as BiliUserSelectorOption[]
|
||||||
|
|
||||||
|
emit('userInfoLoaded', userInfo)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedUserInfo.value = null
|
||||||
|
emit('userInfoLoaded', null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('加载用户信息失败:', error)
|
||||||
|
selectedUserInfo.value = null
|
||||||
|
emit('userInfoLoaded', null)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖搜索函数
|
||||||
|
const debouncedSearch = useDebounceFn(async (value: string) => {
|
||||||
|
const uid = Number.parseInt(value)
|
||||||
|
if (Number.isNaN(uid) || uid <= 0) {
|
||||||
|
options.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadUserInfo(uid)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
// 处理输入变化
|
||||||
|
function handleInput(value: string) {
|
||||||
|
inputValue.value = value
|
||||||
|
const uid = Number.parseInt(value)
|
||||||
|
|
||||||
|
if (Number.isNaN(uid) || uid <= 0) {
|
||||||
|
model.value = undefined
|
||||||
|
selectedUserInfo.value = null
|
||||||
|
options.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有效的数字输入时,立即同步给外部 v-model
|
||||||
|
model.value = uid
|
||||||
|
|
||||||
|
debouncedSearch(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理选择
|
||||||
|
function handleSelect(value: string) {
|
||||||
|
inputValue.value = value
|
||||||
|
const numeric = Number.parseInt(value)
|
||||||
|
model.value = Number.isNaN(numeric) ? undefined : numeric
|
||||||
|
const option = options.value.find(opt => opt.value === value)
|
||||||
|
if (option?.userInfo) {
|
||||||
|
selectedUserInfo.value = option.userInfo
|
||||||
|
emit('userInfoLoaded', option.userInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义渲染选项
|
||||||
|
function renderOption(option: { option: BiliUserSelectorOption }) {
|
||||||
|
const { userInfo } = option.option
|
||||||
|
if (!userInfo) {
|
||||||
|
return h(NText, { depth: 3 }, { default: () => '加载中...' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
NFlex,
|
||||||
|
{ align: 'center', gap: 8 },
|
||||||
|
{
|
||||||
|
default: () => [
|
||||||
|
h(NAvatar, {
|
||||||
|
src: userInfo.face,
|
||||||
|
size: 32,
|
||||||
|
round: true,
|
||||||
|
imgProps: {
|
||||||
|
referrerpolicy: 'no-referrer'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h(
|
||||||
|
NFlex,
|
||||||
|
{ vertical: true, gap: 2 },
|
||||||
|
{
|
||||||
|
default: () => [
|
||||||
|
h(NText, { strong: true }, { default: () => userInfo.name }),
|
||||||
|
h(NText, { depth: 3, size: 'small' }, { default: () => `UID: ${userInfo.mid}` }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算当前显示的值 - 只显示UID
|
||||||
|
const displayValue = computed(() => {
|
||||||
|
return inputValue.value
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NAutoComplete
|
||||||
|
:value="displayValue"
|
||||||
|
:options="options"
|
||||||
|
:loading="loading"
|
||||||
|
:placeholder="placeholder || '请输入B站用户UID'"
|
||||||
|
:size="size || 'medium'"
|
||||||
|
:disabled="disabled"
|
||||||
|
clearable
|
||||||
|
:render-option="renderOption"
|
||||||
|
@update:value="handleInput"
|
||||||
|
@select="handleSelect"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -37,6 +37,7 @@ import { QueryGetAPI } from '@/api/query'
|
|||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
import { objectsToCSV } from '@/Utils'
|
import { objectsToCSV } from '@/Utils'
|
||||||
import PointUserDetailCard from './PointUserDetailCard.vue'
|
import PointUserDetailCard from './PointUserDetailCard.vue'
|
||||||
|
import BiliUserSelector from '@/components/common/BiliUserSelector.vue'
|
||||||
|
|
||||||
// 用户积分设置类型定义
|
// 用户积分设置类型定义
|
||||||
interface PointUserSettings {
|
interface PointUserSettings {
|
||||||
@@ -73,6 +74,7 @@ const isLoading = ref(true)
|
|||||||
const addPointCount = ref(0)
|
const addPointCount = ref(0)
|
||||||
const addPointReason = ref<string>('')
|
const addPointReason = ref<string>('')
|
||||||
const addPointTarget = ref<number>()
|
const addPointTarget = ref<number>()
|
||||||
|
const selectedTargetUserName = ref<string>()
|
||||||
|
|
||||||
// 重置所有积分确认
|
// 重置所有积分确认
|
||||||
const resetConfirmText = ref('')
|
const resetConfirmText = ref('')
|
||||||
@@ -125,7 +127,7 @@ const userStats = computed(() => {
|
|||||||
total: users.value.length,
|
total: users.value.length,
|
||||||
authed: users.value.filter(u => u.isAuthed).length,
|
authed: users.value.filter(u => u.isAuthed).length,
|
||||||
totalPoints: Number(totalPoints.toFixed(1)),
|
totalPoints: Number(totalPoints.toFixed(1)),
|
||||||
totalOrders: users.value.reduce((sum, u) => sum + (u.orderCount || 0), 0),
|
totalOrders: users.value.reduce((sum, u) => sum + ((u.orderCount || 0) > 0 ? (u.orderCount || 0) : 0), 0),
|
||||||
avgPoints: Number(avgPoints.toFixed(1)),
|
avgPoints: Number(avgPoints.toFixed(1)),
|
||||||
filtered: filteredUsers.value.length,
|
filtered: filteredUsers.value.length,
|
||||||
}
|
}
|
||||||
@@ -282,14 +284,16 @@ async function givePoint() {
|
|||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = await QueryGetAPI(`${POINT_API_URL}give-point`, {
|
const data = await QueryGetAPI<{ totalPoint: number, userName?: string, uId?: number }>(`${POINT_API_URL}give-point`, {
|
||||||
uId: addPointTarget.value,
|
uId: addPointTarget.value,
|
||||||
count: addPointCount.value,
|
count: addPointCount.value,
|
||||||
reason: addPointReason.value || '',
|
reason: addPointReason.value || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('添加成功')
|
const userName = data.data?.userName || selectedTargetUserName.value || `UID: ${addPointTarget.value}`
|
||||||
|
const action = addPointCount.value > 0 ? '添加' : '扣除'
|
||||||
|
message.success(`成功为 ${userName} ${action}了 ${Math.abs(addPointCount.value)} 积分`)
|
||||||
showGivePointModal.value = false
|
showGivePointModal.value = false
|
||||||
|
|
||||||
// 重新加载用户数据
|
// 重新加载用户数据
|
||||||
@@ -301,6 +305,7 @@ async function givePoint() {
|
|||||||
addPointCount.value = 0
|
addPointCount.value = 0
|
||||||
addPointReason.value = ''
|
addPointReason.value = ''
|
||||||
addPointTarget.value = undefined
|
addPointTarget.value = undefined
|
||||||
|
selectedTargetUserName.value = undefined
|
||||||
} else {
|
} else {
|
||||||
message.error(`添加失败: ${data.message}`)
|
message.error(`添加失败: ${data.message}`)
|
||||||
}
|
}
|
||||||
@@ -615,21 +620,26 @@ onMounted(async () => {
|
|||||||
align="center"
|
align="center"
|
||||||
:gap="8"
|
:gap="8"
|
||||||
>
|
>
|
||||||
<NInputGroup style="max-width: 300px">
|
<NFlex
|
||||||
<NInputGroupLabel> 目标用户 </NInputGroupLabel>
|
vertical
|
||||||
<NInputNumber
|
:gap="4"
|
||||||
|
style="flex: 1"
|
||||||
|
>
|
||||||
|
<NText depth="3">
|
||||||
|
目标用户
|
||||||
|
</NText>
|
||||||
|
<BiliUserSelector
|
||||||
v-model:value="addPointTarget"
|
v-model:value="addPointTarget"
|
||||||
type="number"
|
placeholder="请输入B站用户UID"
|
||||||
placeholder="请输入目标用户UID"
|
@user-info-loaded="(userInfo) => selectedTargetUserName = userInfo?.name"
|
||||||
min="0"
|
|
||||||
/>
|
/>
|
||||||
</NInputGroup>
|
</NFlex>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NIcon :component="Info24Filled" />
|
<NIcon :component="Info24Filled" />
|
||||||
</template>
|
</template>
|
||||||
<div class="tooltip-content">
|
<div class="tooltip-content">
|
||||||
<p>如果目标用户没在直播间发言过则无法显示用户名, 不过不影响使用</p>
|
<p>输入UID后会自动从B站获取用户信息</p>
|
||||||
<p>因为UID和B站提供的OpenID不兼容, 未认证用户可能会出现两个记录, 不过在认证完成后会合并成一个</p>
|
<p>因为UID和B站提供的OpenID不兼容, 未认证用户可能会出现两个记录, 不过在认证完成后会合并成一个</p>
|
||||||
</div>
|
</div>
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
|
|||||||
@@ -1025,15 +1025,13 @@ export const Config = defineTemplateConfig([
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Reuse existing styles where possible */
|
/* Filter Button Groups */
|
||||||
|
|
||||||
/* --- Styles for Filter Button Groups --- */
|
|
||||||
.filter-button-group {
|
.filter-button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap; /* Allow buttons to wrap */
|
flex-wrap: wrap;
|
||||||
gap: 8px; /* Spacing between buttons */
|
gap: 8px;
|
||||||
margin-bottom: 10px; /* Spacing below the group */
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-label {
|
.filter-label {
|
||||||
@@ -1041,24 +1039,23 @@ export const Config = defineTemplateConfig([
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #555555;
|
color: #555555;
|
||||||
white-space: nowrap; /* Prevent label from wrapping */
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .filter-label {
|
html.dark .filter-label {
|
||||||
color: var(--text-color-2);
|
color: var(--text-color-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for individual language/tag buttons */
|
|
||||||
.filter-button {
|
.filter-button {
|
||||||
padding: 4px 12px; /* Smaller padding */
|
padding: 4px 12px;
|
||||||
border: 1px solid transparent; /* Start transparent */
|
border: 1px solid transparent;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background-color: rgba(0, 0, 0, 0.04); /* Subtle background */
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
font-size: 0.85em; /* Slightly smaller font */
|
font-size: 0.85em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||||
color: #555555;
|
color: #555555;
|
||||||
line-height: 1.4; /* Adjust line height */
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .filter-button {
|
html.dark .filter-button {
|
||||||
@@ -1069,32 +1066,30 @@ html.dark .filter-button {
|
|||||||
.filter-button.active {
|
.filter-button.active {
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
border-color: var(--primary-color); /* Add border for active */
|
border-color: var(--primary-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-button:hover:not(.active) {
|
.filter-button:hover:not(.active) {
|
||||||
background-color: rgba(0, 0, 0, 0.08);
|
background-color: rgba(0, 0, 0, 0.08);
|
||||||
border-color: rgba(0, 0, 0, 0.1); /* Subtle border on hover */
|
border-color: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .filter-button:hover:not(.active) {
|
html.dark .filter-button:hover:not(.active) {
|
||||||
background-color: var(--item-color-hover);
|
background-color: var(--item-color-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Divider between filters and search bar */
|
|
||||||
.filter-divider {
|
.filter-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
margin: 10px 0 15px 0; /* Add margin */
|
margin: 10px 0 15px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .filter-divider {
|
html.dark .filter-divider {
|
||||||
background-color: var(--border-color);
|
background-color: var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Adjustments for Filter/Search Bar --- */
|
|
||||||
.song-list-filter {
|
.song-list-filter {
|
||||||
/* NFlex handles alignment, wrap defaults */
|
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,9 +1116,8 @@ html.dark .search-icon {
|
|||||||
.filter-input {
|
.filter-input {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 6px 15px 6px 30px; /* Left padding for icon */
|
padding: 6px 15px 6px 30px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
/* width: 250px; */ /* Let flexbox handle width or use min-width */
|
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
@@ -1152,7 +1146,7 @@ html.dark .filter-input::placeholder {
|
|||||||
box-shadow: 0 0 0 2px var(--primary-color-a3);
|
box-shadow: 0 0 0 2px var(--primary-color-a3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Naive UI Select Styling (Keep existing) --- */
|
/* Naive UI Select Styling */
|
||||||
:deep(.song-list-filter .n-select .n-base-selection) {
|
:deep(.song-list-filter .n-select .n-base-selection) {
|
||||||
--n-height: 30px !important;
|
--n-height: 30px !important;
|
||||||
--n-padding-single: 0 26px 0 10px !important;
|
--n-padding-single: 0 26px 0 10px !important;
|
||||||
@@ -1165,100 +1159,88 @@ html.dark .filter-input::placeholder {
|
|||||||
line-height: 30px !important;
|
line-height: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Clear Button Styling --- */
|
|
||||||
.clear-button {
|
.clear-button {
|
||||||
height: 30px; /* Match input height */
|
height: 30px;
|
||||||
/* padding: 0 10px; */ /* NButton handles padding well */
|
border-radius: 15px;
|
||||||
border-radius: 15px; /* Match input style */
|
font-size: 0.85em;
|
||||||
font-size: 0.85em; /* Slightly smaller */
|
line-height: 28px;
|
||||||
line-height: 28px; /* Adjust if needed */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Random Button Styling (Keep Existing) --- */
|
|
||||||
.refresh-button {
|
.refresh-button {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
/* padding: 0 15px; Use NButton padding */
|
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- MODIFIED: Structure Styles --- */
|
/* Structure Styles */
|
||||||
|
|
||||||
/* --- NEW: Outer Background & Blur Wrapper --- */
|
|
||||||
.song-list-background-wrapper {
|
.song-list-background-wrapper {
|
||||||
position: relative; /* Anchor for ::before */
|
position: relative;
|
||||||
height: calc(100vh - var(--vtsuru-header-height) - var(--vtsuru-content-padding) - var(--vtsuru-content-padding));
|
height: calc(100vh - var(--vtsuru-header-height) - var(--vtsuru-content-padding) - var(--vtsuru-content-padding));
|
||||||
border-radius: 8px; /* Apply rounding here */
|
border-radius: 8px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-attachment: fixed; /* Keep background fixed */
|
background-attachment: fixed;
|
||||||
overflow: hidden; /* Clip the ::before pseudo-element */
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Blur effect on the wrapper's ::before */
|
|
||||||
.song-list-background-wrapper::before {
|
.song-list-background-wrapper::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
-webkit-backdrop-filter: blur(5px);
|
-webkit-backdrop-filter: blur(5px);
|
||||||
background-color: rgba(80, 80, 80, 0.1); /* Optional overlay */
|
background-color: rgba(80, 80, 80, 0.1);
|
||||||
border-radius: inherit; /* Inherit rounding */
|
border-radius: inherit;
|
||||||
z-index: 1; /* Below content */
|
z-index: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .song-list-background-wrapper::before {
|
html.dark .song-list-background-wrapper::before {
|
||||||
background-color: rgba(255, 255, 255, 0.05); /* Dark mode overlay */
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- MODIFIED: Inner Scrolling Container --- */
|
|
||||||
.song-list-template {
|
.song-list-template {
|
||||||
height: 100%; /* Fill the wrapper */
|
height: 100%;
|
||||||
overflow-y: auto; /* Enable vertical scrolling for content */
|
overflow-y: auto;
|
||||||
position: relative; /* Needed for z-index */
|
position: relative;
|
||||||
z-index: 2; /* Place above the ::before blur layer */
|
z-index: 2;
|
||||||
background: transparent !important; /* Ensure no background color obscures the wrapper */
|
background: transparent !important;
|
||||||
border-radius: inherit; /* Inherit rounding for scrollbar area */
|
border-radius: inherit;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
/* Keep scrollbar styles */
|
|
||||||
&::-webkit-scrollbar { width: 8px; }
|
&::-webkit-scrollbar { width: 8px; }
|
||||||
&::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.05); border-radius: 4px; }
|
&::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.05); border-radius: 4px; }
|
||||||
&::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 4px; }
|
&::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 4px; }
|
||||||
&::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.3); }
|
&::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.3); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode scrollbar styles for the scrolling container */
|
|
||||||
html.dark .song-list-template {
|
html.dark .song-list-template {
|
||||||
&::-webkit-scrollbar-track { background: var(--scrollbar-color); }
|
&::-webkit-scrollbar-track { background: var(--scrollbar-color); }
|
||||||
&::-webkit-scrollbar-thumb { background: var(--scrollbar-color-hover); }
|
&::-webkit-scrollbar-thumb { background: var(--scrollbar-color-hover); }
|
||||||
&::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-color-active); }
|
&::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-color-active); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- MODIFIED: Main Content Container --- */
|
|
||||||
.profile-card-container {
|
.profile-card-container {
|
||||||
position: relative; /* Keep for potential absolute children if any */
|
position: relative;
|
||||||
/* z-index: 2; */ /* Removed: Handled by .song-list-template */
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
/* height: 100%; */ /* Removed: Let content define height */
|
min-height: 100%;
|
||||||
min-height: 100%; /* Ensure it tries to fill the scroll container */
|
box-sizing: border-box;
|
||||||
box-sizing: border-box; /* Include padding in height calculation */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .profile-card-container {
|
html.dark .profile-card-container {
|
||||||
color: var(--text-color-1);
|
color: var(--text-color-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Profile Hover Area Styles (Unchanged) --- */
|
/* Profile Hover Area */
|
||||||
.profile-hover-area {
|
.profile-hover-area {
|
||||||
position: relative; display: flex; align-items: flex-start;
|
position: relative; display: flex; align-items: flex-start;
|
||||||
width: fit-content; min-width: 300px; margin: 0 auto 20px auto;
|
width: fit-content; min-width: 300px; margin: 0 auto 20px auto;
|
||||||
padding: 15px; border-radius: 15px;
|
padding: 15px; border-radius: 15px;
|
||||||
transition: transform 0.4s ease-in-out; z-index: 100; /* High z-index for hover effect */
|
transition: transform 0.4s ease-in-out; z-index: 100;
|
||||||
box-shadow: var(--box-shadow-1);
|
box-shadow: var(--box-shadow-1);
|
||||||
background-color: rgba(255, 255, 255, 0.65);
|
background-color: rgba(255, 255, 255, 0.65);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
@@ -1314,7 +1296,7 @@ html.dark .profile-extra-info {
|
|||||||
.social-links {
|
.social-links {
|
||||||
position: absolute; top: 5px; left: calc(100px + 20px + 10px);
|
position: absolute; top: 5px; left: calc(100px + 20px + 10px);
|
||||||
width: 380px; padding: 15px 20px; border-radius: 10px;
|
width: 380px; padding: 15px 20px; border-radius: 10px;
|
||||||
box-shadow: var(--box-shadow-2); z-index: 20; /* High z-index within hover area */
|
box-shadow: var(--box-shadow-2); z-index: 20;
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
opacity: 0; visibility: hidden; transform: translateX(20px);
|
opacity: 0; visibility: hidden; transform: translateX(20px);
|
||||||
@@ -1325,10 +1307,21 @@ html.dark .social-links {
|
|||||||
background-color: rgba(50, 50, 50, 0.85);
|
background-color: rgba(50, 50, 50, 0.85);
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
border-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
.social-links-title{ font-weight: bold; color: #4d4d4d;}
|
|
||||||
.social-links-subtitle{ white-space: pre-wrap; color: #4d4d4d;}
|
|
||||||
|
|
||||||
html.dark .social-links-title, html.dark .social-links-subtitle { color: var(--text-color-2); }
|
.social-links-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4d4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-links-subtitle {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: #4d4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .social-links-title,
|
||||||
|
html.dark .social-links-subtitle {
|
||||||
|
color: var(--text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
.social-icons-bar {
|
.social-icons-bar {
|
||||||
position: absolute; top: 15px; right: 20px; display: flex; gap: 8px;
|
position: absolute; top: 15px; right: 20px; display: flex; gap: 8px;
|
||||||
@@ -1339,19 +1332,38 @@ html.dark .social-links-title, html.dark .social-links-subtitle { color: var(--t
|
|||||||
font-size: 1em; cursor: pointer; color: #555555; transition: color 0.2s;
|
font-size: 1em; cursor: pointer; color: #555555; transition: color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .social-icons-bar .icon { color: var(--text-color-2); }
|
html.dark .social-icons-bar .icon {
|
||||||
.social-icons-bar .icon:hover { color: var(--primary-color); }
|
color: var(--text-color-2);
|
||||||
.social-icons-bar .icon svg { display: block; width: 100%; height: 100%; fill: currentColor; }
|
}
|
||||||
|
|
||||||
|
.social-icons-bar .icon:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icons-bar .icon svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
.social-grid {
|
.social-grid {
|
||||||
display: grid; grid-template-columns: repeat(2, 1fr);
|
display: grid;
|
||||||
gap: 8px 15px; margin-top: 45px;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px 15px;
|
||||||
|
margin-top: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-link {
|
.social-link {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex;
|
||||||
padding: 5px 8px; border-radius: 5px; font-size: 0.85em; text-decoration: none;
|
align-items: center;
|
||||||
color: #0066cc; background-color: rgba(0, 102, 204, 0.1);
|
justify-content: space-between;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #0066cc;
|
||||||
|
background-color: rgba(0, 102, 204, 0.1);
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1360,23 +1372,46 @@ html.dark .social-link {
|
|||||||
background-color: rgba(var(--primary-color-rgb), 0.15);
|
background-color: rgba(var(--primary-color-rgb), 0.15);
|
||||||
border: 1px solid rgba(122, 159, 197, 0.2);
|
border: 1px solid rgba(122, 159, 197, 0.2);
|
||||||
}
|
}
|
||||||
.social-link:hover { background-color: rgba(0, 102, 204, 0.2); }
|
|
||||||
html.dark .social-link:hover { background-color: rgba(var(--primary-color-rgb), 0.25); }
|
|
||||||
.social-link span:first-child { margin-right: 5px; }
|
|
||||||
.social-link .arrow { font-weight: bold; color: #aaaaaa; }
|
|
||||||
html.dark .social-link .arrow { color: var(--text-color-3); }
|
|
||||||
|
|
||||||
.profile-hover-area.is-hovering .profile-avatar { transform: translateX(-60px); }
|
.social-link:hover {
|
||||||
|
background-color: rgba(0, 102, 204, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .social-link:hover {
|
||||||
|
background-color: rgba(var(--primary-color-rgb), 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link span:first-child {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link .arrow {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #aaaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .social-link .arrow {
|
||||||
|
color: var(--text-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-hover-area.is-hovering .profile-avatar {
|
||||||
|
transform: translateX(-60px);
|
||||||
|
}
|
||||||
|
|
||||||
.profile-hover-area.is-hovering .social-links {
|
.profile-hover-area.is-hovering .social-links {
|
||||||
opacity: 1; visibility: visible; transform: translateX(0);
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateX(0);
|
||||||
left: calc(100px - 60px + 15px);
|
left: calc(100px - 60px + 15px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Song List Container Styles (Unchanged except background/border) --- */
|
/* Song List Container */
|
||||||
.song-list-container {
|
.song-list-container {
|
||||||
padding: 15px 25px; border-radius: 15px; font-family: sans-serif;
|
padding: 15px 25px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-family: sans-serif;
|
||||||
box-shadow: var(--box-shadow-1);
|
box-shadow: var(--box-shadow-1);
|
||||||
background-color: rgba(255, 255, 255, 0.50); /* Matched profile */
|
background-color: rgba(255, 255, 255, 0.50);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1385,33 +1420,36 @@ html.dark .song-list-container {
|
|||||||
border-color: rgba(255, 255, 255, 0.1);
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Table Styles (Unchanged) --- */
|
/* Table Styles */
|
||||||
.song-table-wrapper {
|
.song-table-wrapper {
|
||||||
overflow-y: auto; /* This overflow handles table content, not main scroll */
|
overflow-y: auto;
|
||||||
/* min-height: 200px; */ /* Might not be needed if max-height is set */
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
/* Scrollbar styling specific to this inner table scroll if needed */
|
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-list-table {
|
.song-list-table {
|
||||||
width: 100%; border-collapse: collapse; font-size: 0.9em;
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.9em;
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-list-table thead th {
|
.song-list-table thead th {
|
||||||
position: sticky; top: 0; z-index: 1; /* Sticky within .song-table-wrapper */
|
position: sticky;
|
||||||
padding: 10px 12px; text-align: left; font-weight: 600;
|
top: 0;
|
||||||
background-color: rgba(245, 245, 245, 0.8); /* Slightly less transparent */
|
z-index: 1;
|
||||||
backdrop-filter: blur(2px); /* Blur header slightly */
|
padding: 10px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: rgba(245, 245, 245, 0.8);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
color: #444444;
|
color: #444444;
|
||||||
user-select: none; /* Prevent text selection on click */
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .song-list-table thead th {
|
html.dark .song-list-table thead th {
|
||||||
background-color: rgba(55, 55, 55, 0.85); /* Dark mode header bg */
|
background-color: rgba(55, 55, 55, 0.85);
|
||||||
border-bottom-color: var(--border-color);
|
border-bottom-color: var(--border-color);
|
||||||
color: var(--text-color-2);
|
color: var(--text-color-2);
|
||||||
}
|
}
|
||||||
@@ -1423,13 +1461,16 @@ html.dark .song-list-table thead th {
|
|||||||
.song-list-table th:nth-child(5) { width: 15%; }
|
.song-list-table th:nth-child(5) { width: 15%; }
|
||||||
.song-list-table th:nth-child(6) { width: 25%; }
|
.song-list-table th:nth-child(6) { width: 25%; }
|
||||||
|
|
||||||
.song-list-table tbody tr { transition: background-color 0.15s ease; }
|
.song-list-table tbody tr {
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.song-list-table tbody td {
|
.song-list-table tbody td {
|
||||||
padding: 10px 12px; vertical-align: middle;
|
padding: 10px 12px;
|
||||||
|
vertical-align: middle;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
color: #4d4d4d;
|
color: #4d4d4d;
|
||||||
word-break: break-word; /* Prevent long text overflow */
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .song-list-table tbody td {
|
html.dark .song-list-table tbody td {
|
||||||
@@ -1437,110 +1478,107 @@ html.dark .song-list-table tbody td {
|
|||||||
color: var(--text-color-2);
|
color: var(--text-color-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-list-table tbody tr:nth-child(even) { background-color: rgba(0, 0, 0, 0.02); }
|
.song-list-table tbody tr:nth-child(even) {
|
||||||
html.dark .song-list-table tbody tr:nth-child(even) { background-color: var(--item-color-striped); }
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
.song-list-table tbody tr:hover { background-color: rgba(0, 0, 0, 0.05); }
|
}
|
||||||
html.dark .song-list-table tbody tr:hover { background-color: var(--item-color-hover); }
|
|
||||||
|
html.dark .song-list-table tbody tr:nth-child(even) {
|
||||||
|
background-color: var(--item-color-striped);
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-list-table tbody tr:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .song-list-table tbody tr:hover {
|
||||||
|
background-color: var(--item-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
.song-name {
|
.song-name {
|
||||||
display: flex; align-items: center; gap: 8px; font-weight: 600;
|
display: flex;
|
||||||
text-decoration: none; color: #2c2c2c;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2c2c2c;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .song-name { color: var(--text-color-1); }
|
html.dark .song-name {
|
||||||
|
color: var(--text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
.artist-link {
|
.artist-link {
|
||||||
padding: 1px 0; cursor: pointer; text-decoration: none;
|
padding: 1px 0;
|
||||||
color: var(--text-color-2); /* Use theme primary for links */
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color-2);
|
||||||
transition: color 0.2s ease, text-decoration 0.2s ease;
|
transition: color 0.2s ease, text-decoration 0.2s ease;
|
||||||
}
|
}
|
||||||
.artist-link:hover { text-decoration: underline; }
|
|
||||||
|
.artist-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.song-name :deep(.n-button .n-icon),
|
.song-name :deep(.n-button .n-icon),
|
||||||
.song-name :deep(.n-button .svg-icon) {
|
.song-name :deep(.n-button .svg-icon) {
|
||||||
color: currentColor !important; fill: currentColor !important;
|
color: currentColor !important;
|
||||||
|
fill: currentColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
display: inline-block; width: 1em; height: 1em; vertical-align: -0.15em;
|
display: inline-block;
|
||||||
fill: currentColor; overflow: hidden;
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-results td {
|
.no-results td {
|
||||||
padding: 30px 12px; text-align: center; font-style: italic; color: #999999;
|
padding: 30px 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
color: #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .no-results td { color: var(--text-color-3); }
|
html.dark .no-results td {
|
||||||
|
color: var(--text-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure NTag in table wraps properly */
|
|
||||||
.song-list-table td .n-tag {
|
.song-list-table td .n-tag {
|
||||||
margin-bottom: 2px; /* Add slight spacing if tags wrap */
|
margin-bottom: 2px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- NEW: Selected Artist Highlight --- */
|
|
||||||
.artist-link.selected-artist {
|
|
||||||
background-color: var(--primary-color-a4); /* 增加背景不透明度 */
|
|
||||||
border: 1px solid var(--primary-color-a6); /* 添加边框 */
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 1px 3px; /* 调整内边距,使边框更明显 */
|
|
||||||
border-radius: 4px; /* 轻微调整圆角 */
|
|
||||||
color: var(--primary-color-dark); /* 亮色模式下使用较深的主题色文字 */
|
|
||||||
/* text-decoration: underline; */ /* 如果需要可以取消注释 */
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .artist-link.selected-artist {
|
|
||||||
background-color: var(--primary-color-a6); /* 增加背景不透明度 */
|
|
||||||
border: 1px solid var(--primary-color-a8); /* 添加边框 */
|
|
||||||
color: var(--primary-color-light); /* 暗色模式下使用亮色文字 */
|
|
||||||
}
|
|
||||||
/* --- END: Selected Artist Highlight --- */
|
|
||||||
|
|
||||||
/* Base style for clickable language */
|
/* Base style for clickable language */
|
||||||
.language-link {
|
.language-link {
|
||||||
padding: 1px 0;
|
padding: 1px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text-color-2); /* Use theme primary for links */
|
color: var(--text-color-2);
|
||||||
transition: color 0.2s ease, text-decoration 0.2s ease;
|
transition: color 0.2s ease, text-decoration 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-link:hover {
|
.language-link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- NEW: Selected Artist/Language Highlight --- */
|
/* Selected Artist/Language Highlight */
|
||||||
.artist-link.selected-artist,
|
.artist-link.selected-artist,
|
||||||
.language-link.selected-language {
|
.language-link.selected-language {
|
||||||
background-color: var(--primary-color-a4); /* 增加背景不透明度 */
|
background-color: var(--primary-color-a4);
|
||||||
border: 1px solid var(--primary-color-a6); /* 添加边框 */
|
border: 1px solid var(--primary-color-a6);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 1px 3px; /* 调整内边距,使边框更明显 */
|
padding: 1px 3px;
|
||||||
border-radius: 4px; /* 轻微调整圆角 */
|
border-radius: 4px;
|
||||||
color: var(--primary-color-dark); /* 亮色模式下使用较深的主题色文字 */
|
color: var(--primary-color-dark);
|
||||||
/* text-decoration: underline; */ /* 如果需要可以取消注释 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .artist-link.selected-artist,
|
html.dark .artist-link.selected-artist,
|
||||||
html.dark .language-link.selected-language {
|
html.dark .language-link.selected-language {
|
||||||
background-color: var(--primary-color-a6); /* 增加背景不透明度 */
|
background-color: var(--primary-color-a6);
|
||||||
border: 1px solid var(--primary-color-a8); /* 添加边框 */
|
border: 1px solid var(--primary-color-a8);
|
||||||
color: var(--primary-color-light); /* 暗色模式下使用亮色文字 */
|
color: var(--primary-color-light);
|
||||||
}
|
|
||||||
/* --- END: Selected Artist/Language Highlight --- */
|
|
||||||
|
|
||||||
.song-name :deep(.n-button .n-icon),
|
|
||||||
.song-name :deep(.n-button .svg-icon) {
|
|
||||||
color: currentColor !important; fill: currentColor !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- NEW: Style for empty placeholders --- */
|
|
||||||
.empty-placeholder {
|
|
||||||
color: #999999; /* Use a standard gray color */
|
|
||||||
font-style: italic; /* Optional: make it italic */
|
|
||||||
font-size: 0.9em; /* Optional: slightly smaller */
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .empty-placeholder {
|
|
||||||
color: var(--text-color-3); /* Use theme variable for dark mode */
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user