fix(ui+permission): 移除联合投稿 / emoji换SVG / 修复移动端浅色适配 / 已排期显示日期 / 隐藏.env提示 / 公开排期档期游客访问
具体改动: 1. feat(投稿表单): 移除 RequestForm.vue 联合投稿功能整套模板+状态+payload+CSS(10处残留) 2. feat(图标): Icon.vue 新增 sparkles/star 图标; 首页档期卡片📅🎵✨和排期列表空状态🎵📅换成 lucide SVG Icon 3. fix(浅色适配): ScheduleList.vue 日期选择弹窗桌面端+移动端(content/header/h3/close-btn/item)全部改为 bg-primary + var text + indigo 主色, 修复竖屏选择日期标题白字看不见 4. feat(歌曲列表): SongList.vue scheduled-tag 内联追加 formatScheduleDate, 徽章+日期双胶囊显示 5. fix(登录): OAuthButtons.vue showPlaceholderHint 强制 false, 隐藏虚线框 .env 配置提示 6. fix(权限): server/middleware/auth.ts + auth-route-policy.ts 双白名单同步追加 /api/expected-play-dates GET 公共访问, 修复未登录 Authentication expired 401
This commit is contained in:
@@ -70,7 +70,7 @@ const hasEnabledProviders = computed(() => {
|
||||
return config.public.oauth && Object.values(config.public.oauth).some((enabled) => enabled)
|
||||
})
|
||||
|
||||
const showPlaceholderHint = computed(() => props.alwaysShow && !hasEnabledProviders.value)
|
||||
const showPlaceholderHint = computed(() => false)
|
||||
|
||||
const loginWith = (provider: string) => {
|
||||
navigateTo(`/api/auth/${provider}`, { external: true })
|
||||
|
||||
@@ -128,31 +128,6 @@
|
||||
<span class="btn-text">从往期导入</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 联合投稿人区域 -->
|
||||
<div v-if="user && enableCollaborativeSubmission" class="collaborators-section">
|
||||
<div class="section-label">联合投稿</div>
|
||||
<div class="collaborators-list">
|
||||
<div v-for="user in collaborators" :key="user.id" class="collaborator-tag">
|
||||
<span class="collaborator-name">{{ user.name }}</span>
|
||||
<button
|
||||
class="remove-collaborator"
|
||||
type="button"
|
||||
@click="removeCollaborator(user.id)"
|
||||
>
|
||||
<Icon :size="12" name="close" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="add-collaborator-btn"
|
||||
type="button"
|
||||
@click="showUserSearchModal = true"
|
||||
>
|
||||
<Icon :size="14" name="plus" />
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="expectedPlayDateOptions.length > 0" class="expected-play-section">
|
||||
@@ -652,16 +627,6 @@
|
||||
@import-success="handleImportSuccess"
|
||||
/>
|
||||
|
||||
<!-- 用户搜索弹窗 -->
|
||||
<UserSearchModal
|
||||
v-if="enableCollaborativeSubmission"
|
||||
v-model:show="showUserSearchModal"
|
||||
:exclude-ids="[user?.id, ...collaborators.map((u) => u.id)]"
|
||||
:multiple="true"
|
||||
title="添加联合投稿人"
|
||||
@select="handleUserSelect"
|
||||
/>
|
||||
|
||||
<!-- 手动输入弹窗 -->
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
@@ -853,7 +818,6 @@ import Icon from '../UI/Icon.vue'
|
||||
import { convertToHttps, validateUrl } from '~/utils/url'
|
||||
|
||||
import ImportSongsModal from './ImportSongsModal.vue'
|
||||
import UserSearchModal from '../Common/UserSearchModal.vue'
|
||||
|
||||
const props = defineProps({
|
||||
loading: {
|
||||
@@ -869,7 +833,6 @@ const {
|
||||
guidelines: submissionGuidelines,
|
||||
initSiteConfig,
|
||||
enableReplayRequests,
|
||||
enableCollaborativeSubmission,
|
||||
enableSubmissionRemarks
|
||||
} = useSiteConfig()
|
||||
|
||||
@@ -983,8 +946,6 @@ const selectedPodcastName = ref('')
|
||||
const podcastCookie = ref('')
|
||||
|
||||
const showPlaylistModal = ref(false)
|
||||
const showUserSearchModal = ref(false)
|
||||
const collaborators = ref([])
|
||||
|
||||
const songService = useSongs()
|
||||
const playTimes = ref([])
|
||||
@@ -1045,22 +1006,6 @@ const handleImportSuccess = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleUserSelect = (users) => {
|
||||
if (Array.isArray(users)) {
|
||||
// 过滤掉已存在的
|
||||
const newUsers = users.filter((u) => !collaborators.value.some((c) => c.id === u.id))
|
||||
collaborators.value.push(...newUsers)
|
||||
} else if (users) {
|
||||
if (!collaborators.value.some((c) => c.id === users.id)) {
|
||||
collaborators.value.push(users)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeCollaborator = (userId) => {
|
||||
collaborators.value = collaborators.value.filter((c) => c.id !== userId)
|
||||
}
|
||||
|
||||
// 获取播出时段
|
||||
const fetchPlayTimes = async () => {
|
||||
loadingPlayTimes.value = true
|
||||
@@ -1456,13 +1401,6 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(enableCollaborativeSubmission, (enabled) => {
|
||||
if (!enabled) {
|
||||
showUserSearchModal.value = false
|
||||
collaborators.value = []
|
||||
}
|
||||
})
|
||||
|
||||
watch(enableSubmissionRemarks, (enabled) => {
|
||||
if (!enabled) {
|
||||
submissionNote.value = ''
|
||||
@@ -2288,7 +2226,6 @@ const submitSong = async (result, options = {}) => {
|
||||
playUrl: selectedUrl.value || null,
|
||||
submissionNote: submissionNote.value.trim() || null,
|
||||
submissionNotePublic: submissionNotePublic.value,
|
||||
collaborators: collaborators.value.map((u) => u.id),
|
||||
bilibiliCid: bilibiliCid || null,
|
||||
bilibiliPage: bilibiliPage,
|
||||
expectedPlayDate: expectedPlayDate.value || null,
|
||||
@@ -2678,7 +2615,6 @@ const resetAfterSubmit = () => {
|
||||
manualArtist.value = ''
|
||||
manualCover.value = ''
|
||||
manualPlayUrl.value = ''
|
||||
collaborators.value = []
|
||||
submissionNote.value = ''
|
||||
submissionNotePublic.value = true
|
||||
// 重置URL验证状态
|
||||
@@ -3268,83 +3204,6 @@ defineExpose({
|
||||
box-shadow: 0 2px 6px rgba(79, 70, 229, 0.1);
|
||||
}
|
||||
|
||||
/* 联合投稿人区域 */
|
||||
.collaborators-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-family: 'MiSans', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
color: var(--text-primary, #09090b);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.collaborators-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.collaborator-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(79, 70, 229, 0.08);
|
||||
border: 1px solid rgba(79, 70, 229, 0.2);
|
||||
border-radius: 10px;
|
||||
padding: 0.3rem 0.6rem;
|
||||
font-size: 13px;
|
||||
color: var(--primary, #4f46e5);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.remove-collaborator {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--primary, #4f46e5);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.remove-collaborator:hover {
|
||||
opacity: 1;
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.add-collaborator-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
background: var(--bg-secondary, #f4f4f5);
|
||||
border: 1px solid var(--border-secondary, #e4e4e7);
|
||||
border-radius: 10px;
|
||||
padding: 0.45rem 0.9rem;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary, #52525b);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.add-collaborator-btn:hover {
|
||||
background: var(--primary, #4f46e5);
|
||||
color: #ffffff;
|
||||
border-color: var(--primary, #4f46e5);
|
||||
box-shadow: 0 4px 10px rgba(79, 70, 229, 0.18);
|
||||
}
|
||||
|
||||
/* 自定义复选框样式 */
|
||||
.custom-checkbox-wrapper {
|
||||
display: flex;
|
||||
@@ -5122,20 +4981,6 @@ defineExpose({
|
||||
min-width: unset; /* 移除桌面端的最小宽度限制 */
|
||||
}
|
||||
|
||||
.collaborators-section {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
flex: none;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -104,13 +104,13 @@
|
||||
</div>
|
||||
|
||||
<div v-else-if="!schedules || schedules.length === 0" key="empty-all" class="empty">
|
||||
<div class="icon mb-4">🎵</div>
|
||||
<Icon class="icon mb-4" name="music" :size="48" />
|
||||
<p>暂无排期信息</p>
|
||||
<p class="text-sm text-text-secondary">点歌后等待管理员安排播出时间</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentDateSchedules.length === 0" key="empty-date" class="empty">
|
||||
<div class="icon mb-4">📅</div>
|
||||
<Icon class="icon mb-4" name="calendar" :size="48" />
|
||||
<p>当前日期暂无排期</p>
|
||||
<p>请选择其他日期查看</p>
|
||||
</div>
|
||||
@@ -3168,33 +3168,35 @@ const vRipple = {
|
||||
|
||||
/* 日期选择弹窗 */
|
||||
.date-picker-content {
|
||||
background: var(--surface-primary, var(--bg-primary, #1a1a1f));
|
||||
background: var(--bg-primary, #ffffff);
|
||||
border-radius: 20px 20px 0 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
border: none;
|
||||
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.date-picker-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-bottom: 1px solid var(--border-secondary, rgba(0, 0, 0, 0.06));
|
||||
}
|
||||
|
||||
.date-picker-header h3 {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #09090b);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
background: var(--bg-tertiary, rgba(0, 0, 0, 0.06));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary, #52525b);
|
||||
}
|
||||
|
||||
.date-picker-list {
|
||||
@@ -3205,20 +3207,23 @@ const vRipple = {
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 6px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: var(--bg-secondary, #f4f4f5);
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary, #09090b);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.date-picker-item:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
background: rgba(79, 70, 229, 0.08);
|
||||
transform: none;
|
||||
color: var(--primary, #4f46e5);
|
||||
}
|
||||
|
||||
.date-picker-item.active {
|
||||
background: rgba(11, 90, 254, 0.15);
|
||||
background: rgba(79, 70, 229, 0.14);
|
||||
border-left: none;
|
||||
color: #0b5afe;
|
||||
color: var(--primary, #4f46e5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3453,10 +3458,10 @@ const vRipple = {
|
||||
width: 85%;
|
||||
max-width: 350px;
|
||||
max-height: 70vh;
|
||||
background: var(--bg-secondary, #1a1d24);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: var(--bg-primary, #ffffff);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid var(--border-secondary, #e4e4e7);
|
||||
overflow: hidden;
|
||||
animation: scale-in 0.2s ease;
|
||||
display: flex;
|
||||
@@ -3468,19 +3473,19 @@ const vRipple = {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom: 1px solid var(--border-secondary, rgba(0, 0, 0, 0.08));
|
||||
}
|
||||
|
||||
.date-picker-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
color: var(--text-primary, #09090b);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: var(--text-secondary, #52525b);
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
@@ -3495,24 +3500,28 @@ const vRipple = {
|
||||
|
||||
.date-picker-item {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--bg-secondary, #f4f4f5);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
white-space: pre-line;
|
||||
color: var(--text-primary, #09090b);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.date-picker-item:hover {
|
||||
background: rgba(11, 90, 254, 0.1);
|
||||
background: rgba(79, 70, 229, 0.1);
|
||||
transform: translateY(-2px);
|
||||
color: var(--primary, #4f46e5);
|
||||
}
|
||||
|
||||
.date-picker-item.active {
|
||||
background: rgba(11, 90, 254, 0.2);
|
||||
border-left: 3px solid #0b5afe;
|
||||
background: rgba(79, 70, 229, 0.14);
|
||||
border-left: 3px solid var(--primary, #4f46e5);
|
||||
color: var(--primary, #4f46e5);
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
|
||||
@@ -234,14 +234,16 @@
|
||||
</span>
|
||||
<span
|
||||
v-else-if="song.scheduled"
|
||||
:title="
|
||||
song.scheduleDate
|
||||
? `排期日期:${formatScheduleDate(song.scheduleDate)}`
|
||||
: '已排期'
|
||||
"
|
||||
class="scheduled-tag"
|
||||
class="scheduled-group"
|
||||
>
|
||||
已排期
|
||||
<span class="scheduled-tag">已排期</span>
|
||||
<span
|
||||
v-if="song.scheduleDate"
|
||||
:title="`排期日期:${formatScheduleDate(song.scheduleDate)}`"
|
||||
class="scheduled-date"
|
||||
>
|
||||
{{ formatScheduleDate(song.scheduleDate) }}
|
||||
</span>
|
||||
</span>
|
||||
<span v-else-if="song.isReplay" title="重播歌曲" class="replay-tag"> 重播 </span>
|
||||
<button
|
||||
@@ -2268,17 +2270,36 @@ const vRipple = {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.scheduled-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
margin-left: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.scheduled-tag {
|
||||
display: inline-flex;
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
border: 1px solid rgba(16, 185, 129, 0.4);
|
||||
border-radius: 4px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
font-size: 0.7rem;
|
||||
color: #10b981;
|
||||
margin-left: 0.5rem;
|
||||
flex-shrink: 0; /* 防止标签被压缩 */
|
||||
align-self: center; /* 确保垂直居中 */
|
||||
background: rgba(16, 185, 129, 0.12);
|
||||
border: 1px solid rgba(16, 185, 129, 0.35);
|
||||
border-radius: 6px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.scheduled-date {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary, #52525b);
|
||||
background: var(--bg-secondary, #f4f4f5);
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-secondary, #e4e4e7);
|
||||
}
|
||||
|
||||
.played-tag {
|
||||
|
||||
@@ -157,6 +157,11 @@
|
||||
<polyline points="12,6 12,12 16,14" />
|
||||
</g>
|
||||
|
||||
<!-- 星星/闪耀图标 -->
|
||||
<g v-else-if="name === 'sparkles' || name === 'star'">
|
||||
<path d="M12 3l1.8 5.4L19 9.7l-4.2 3.6L16.6 19 12 15.8 7.4 19l1.8-5.7L5 9.7l5.2-1.3L12 3z" />
|
||||
</g>
|
||||
|
||||
<!-- 用户图标 -->
|
||||
<g v-else-if="name === 'user'">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
|
||||
+3
-3
@@ -109,7 +109,7 @@
|
||||
<div class="schedule-overview-card" aria-label="档期概览">
|
||||
<div class="schedule-overview-grid">
|
||||
<div class="overview-stat">
|
||||
<span class="stat-icon">📅</span>
|
||||
<Icon class="stat-icon" name="calendar" :size="18" />
|
||||
<span class="stat-label">今日可点排期</span>
|
||||
<div class="stat-value">
|
||||
<span>{{ todaySchedulesCount }}</span>
|
||||
@@ -118,7 +118,7 @@
|
||||
<div class="stat-detail">{{ todayLabel }}</div>
|
||||
</div>
|
||||
<div class="overview-stat">
|
||||
<span class="stat-icon">🎵</span>
|
||||
<Icon class="stat-icon" name="music" :size="18" />
|
||||
<span class="stat-label">待播歌曲总数</span>
|
||||
<div class="stat-value">
|
||||
<span>{{ totalSchedulesCount }}</span>
|
||||
@@ -127,7 +127,7 @@
|
||||
<div class="stat-detail">累计审核通过 / 审核中</div>
|
||||
</div>
|
||||
<div class="overview-stat primary">
|
||||
<span class="stat-icon">✨</span>
|
||||
<Icon class="stat-icon" name="sparkles" :size="18" />
|
||||
<span class="stat-label">当前空闲档期</span>
|
||||
<div class="stat-value">
|
||||
<span>{{ totalFreeSlots }}</span>
|
||||
|
||||
Reference in New Issue
Block a user