Skip to content
Merged

Main #82

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 47 additions & 10 deletions public/api/workspace.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,15 @@ function validateWorkspaceContent($content) {
]);

} elseif ($action === 'list') {
// 从权限表获取文件
$fileIds = getUserAccessibleFiles($dbPermissions, $username);

// 兼容旧数据:同时从用户文件列表获取文件
$userFilesKey = sanitizeDbKey($username . '_files');
$legacyFileIds = $dbUsers->get_array($userFilesKey);
if ($legacyFileIds && is_array($legacyFileIds)) {
$fileIds = array_unique(array_merge($fileIds ?: [], $legacyFileIds));
}

$list = [];
if ($fileIds && is_array($fileIds)) {
Expand All @@ -232,6 +240,15 @@ function validateWorkspaceContent($content) {
$fileData = json_decode($fileRaw, true);
if (!$fileData || !isset($fileData['metadata'])) continue;

// 兼容旧数据:自动迁移权限记录
$permKey = sanitizeDbKey("perm_{$fileId}_{$username}");
if ($dbPermissions->get($permKey) === null) {
// 检查文件作者是否是当前用户
if (isset($fileData['metadata']['author']) && $fileData['metadata']['author'] === $username) {
grantFilePermission($dbPermissions, $fileId, $username, 'owner');
}
}

$list[] = [
'fileId' => $fileId,
'metadata' => $fileData['metadata']
Expand All @@ -258,11 +275,6 @@ function validateWorkspaceContent($content) {
respond(['success' => false, 'message' => '文件ID格式无效']);
}

// 检查读权限
if (!hasFilePermission($dbPermissions, $fileId, $username, 'read')) {
respond(['success' => false, 'message' => '无权访问该文件'], 403);
}

// 消毒文件 ID
$sanitizedFileId = sanitizeDbKey($fileId);

Expand All @@ -276,6 +288,20 @@ function validateWorkspaceContent($content) {
respond(['success' => false, 'message' => '文件格式损坏']);
}

// 检查读权限(兼容旧数据:如果没有权限记录,检查文件作者)
$permKey = sanitizeDbKey("perm_{$fileId}_{$username}");
if ($dbPermissions->get($permKey) === null) {
// 没有权限记录,检查文件作者
if (isset($fileData['metadata']['author']) && $fileData['metadata']['author'] === $username) {
// 自动迁移:创建权限记录
grantFilePermission($dbPermissions, $fileId, $username, 'owner');
} else {
respond(['success' => false, 'message' => '无权访问该文件'], 403);
}
} elseif (!hasFilePermission($dbPermissions, $fileId, $username, 'read')) {
respond(['success' => false, 'message' => '无权访问该文件'], 403);
}

respond([
'success' => true,
'data' => $fileData
Expand All @@ -291,15 +317,26 @@ function validateWorkspaceContent($content) {
respond(['success' => false, 'message' => '文件ID格式无效']);
}

// 检查写权限
if (!hasFilePermission($dbPermissions, $fileId, $username, 'write')) {
respond(['success' => false, 'message' => '无权删除该文件'], 403);
}

// 消毒文件 ID
$sanitizedFileId = sanitizeDbKey($fileId);

$fileRaw = $dbFiles->get($sanitizedFileId);

// 检查写权限(兼容旧数据:如果没有权限记录,检查文件作者)
$permKey = sanitizeDbKey("perm_{$fileId}_{$username}");
if ($dbPermissions->get($permKey) === null) {
// 没有权限记录,检查文件作者
$fileData = $fileRaw ? json_decode($fileRaw, true) : null;
if ($fileData && isset($fileData['metadata']['author']) && $fileData['metadata']['author'] === $username) {
// 自动迁移:创建权限记录
grantFilePermission($dbPermissions, $fileId, $username, 'owner');
} else {
respond(['success' => false, 'message' => '无权删除该文件'], 403);
Comment on lines +328 to +334
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In delete, when there is no permission record and $fileRaw is null (file already deleted), $fileData becomes null and the code returns 403 (lines 327-335). This prevents users from removing stale legacy entries, even though the later else branch is designed to handle the “file already removed” case. Consider short-circuiting: if $fileRaw === null, skip the author-based permission migration and proceed to remove the id from the user list / revoke permissions and return success.

Suggested change
// 没有权限记录,检查文件作者
$fileData = $fileRaw ? json_decode($fileRaw, true) : null;
if ($fileData && isset($fileData['metadata']['author']) && $fileData['metadata']['author'] === $username) {
// 自动迁移:创建权限记录
grantFilePermission($dbPermissions, $fileId, $username, 'owner');
} else {
respond(['success' => false, 'message' => '无权删除该文件'], 403);
// 没有权限记录时,仅在文件仍存在时回退检查文件作者;
// 如果文件已不存在,则继续走后续清理逻辑,允许移除陈旧引用。
if ($fileRaw !== null) {
$fileData = json_decode($fileRaw, true);
if ($fileData && isset($fileData['metadata']['author']) && $fileData['metadata']['author'] === $username) {
// 自动迁移:创建权限记录
grantFilePermission($dbPermissions, $fileId, $username, 'owner');
} else {
respond(['success' => false, 'message' => '无权删除该文件'], 403);
}

Copilot uses AI. Check for mistakes.
}
} elseif (!hasFilePermission($dbPermissions, $fileId, $username, 'write')) {
respond(['success' => false, 'message' => '无权删除该文件'], 403);
}

if ($fileRaw !== null) {
$dbFiles->delete($sanitizedFileId);

Expand Down
4 changes: 2 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ onMounted(async () => {

if (isCtrl && e.key === 'z' && !e.shiftKey) {
e.preventDefault()
if (canUndo()) {
if (canUndo.value) {
undo()
}
}

if (isCtrl && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
e.preventDefault()
if (canRedo()) {
if (canRedo.value) {
redo()
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,19 @@
--color-bg-hover: #2a3f5c;
--color-bg-overlay: rgba(0, 0, 0, 0.7);

/* 文字颜色 - 提升禁用文字对比度 */
/* 文字颜色 - 优化深色模式可读性 */
--color-text-primary: #f1f5f9;
--color-text-secondary: #cbd5e1;
--color-text-muted: #94a3b8;
--color-text-disabled: #7b8fa3;
--color-text-muted: #a0aec0;
--color-text-disabled: #8899aa;
--color-text-inverse: #ffffff;

/* 边框颜色 - 提升可见度 */
--color-border: #3d4f63;
--color-border-strong: #536578;
--color-border-hover: #536578;
--color-border-light: #253347;
--color-border-dark: #7b8fa3;
--color-border-dark: #8899aa;

/* 次要背景色 */
--color-bg-secondary: #151d2e;
Expand Down Expand Up @@ -250,15 +250,15 @@

--color-text-primary: #f1f5f9;
--color-text-secondary: #cbd5e1;
--color-text-muted: #94a3b8;
--color-text-disabled: #7b8fa3;
--color-text-muted: #a0aec0;
--color-text-disabled: #8899aa;
--color-text-inverse: #ffffff;

--color-border: #3d4f63;
--color-border-strong: #536578;
--color-border-hover: #536578;
--color-border-light: #253347;
--color-border-dark: #7b8fa3;
--color-border-dark: #8899aa;

--color-bg-secondary: #151d2e;

Expand Down
2 changes: 1 addition & 1 deletion src/components/auth/LoginDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ const handleSubmit = async () => {
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
background: var(--color-bg-overlay);
display: flex;
align-items: center;
justify-content: center;
Expand Down
2 changes: 1 addition & 1 deletion src/components/auth/SyncSettingsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ const handleSubmit = async () => {
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
background: var(--color-bg-overlay);
display: flex;
align-items: center;
justify-content: center;
Expand Down
2 changes: 1 addition & 1 deletion src/components/docs/RuleUsageGuide.vue
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@
padding: 10px 12px;
background: var(--color-info-bg);
border-radius: 8px;
border: 1px solid #bfdbfe;
border: 1px solid var(--color-border);
font-size: 12px;
color: var(--color-info);
line-height: 1.5;
Expand Down
15 changes: 7 additions & 8 deletions src/components/layout/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@

<UnifiedSettingsDialog
v-if="showUnifiedSettings"
:visible="showUnifiedSettings"
@update:visible="showUnifiedSettings = $event"
@save="handleSaveSettings"
/>
</header>
Expand All @@ -80,6 +78,7 @@ import { Cloud, FolderOpen, Github, LogIn, Monitor, Moon, Settings, Sun, User }
import { useAuth } from '@/composables/useAuth'
import { useCloudWorkspaceDialog } from '@/composables/useCloudWorkspaceDialog'
import { useGlobalSettings } from '@/composables/useGlobalSettings'
import { useSettingsDialog } from '@/composables/useSettingsDialog'

const UnifiedSettingsDialog = defineAsyncComponent(() => import('../settings/UnifiedSettingsDialog.vue'))

Expand All @@ -88,9 +87,9 @@ const emit = defineEmits(['open-login'])
const { currentUser, webdavConfig, isLoggedIn, logout, authType, initAuth } = useAuth()
const { openCloudDialog } = useCloudWorkspaceDialog()
const { settings, saveToLocalStorage, applyColorScheme, applyThemeColor } = useGlobalSettings()
const { visible: showUnifiedSettings, openSettings, closeSettings } = useSettingsDialog()

const showDropdown = ref(false)
const showUnifiedSettings = ref(false)
const menuContainer = ref(null)

const hasRetiehe = computed(() => !!currentUser.value)
Expand Down Expand Up @@ -122,7 +121,7 @@ const openWorkspaceManagement = () => {
}

const openUnifiedSettings = () => {
showUnifiedSettings.value = true
openSettings()
showDropdown.value = false
}

Expand All @@ -148,7 +147,7 @@ onMounted(() => {

onBeforeUnmount(() => {
document.removeEventListener('click', closeDropdownOnOutsideClick)
showUnifiedSettings.value = false
closeSettings()
})
</script>

Expand All @@ -162,7 +161,7 @@ onBeforeUnmount(() => {
width: 100%;
background: var(--color-primary);
height: 100px;
color: white;
color: var(--color-text-inverse);
padding: 0 30px;
box-shadow: var(--shadow-md);
gap: 20px;
Expand Down Expand Up @@ -196,7 +195,7 @@ onBeforeUnmount(() => {
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
color: var(--color-text-inverse);
padding: 8px 16px;
border-radius: 24px;
font-size: 14px;
Expand Down Expand Up @@ -334,7 +333,7 @@ onBeforeUnmount(() => {

.theme-btn.active {
background: rgba(255, 255, 255, 0.2);
color: white;
color: var(--color-text-inverse);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

Expand Down
1 change: 0 additions & 1 deletion src/components/layout/EditorPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ const candidatePanelStyle = computed(() => {
flex: 1;
min-height: 0;
background: var(--color-surface);
border-bottom: 2px solid var(--color-border);
overflow: hidden;
display: flex;
flex-direction: column;
Expand Down
14 changes: 8 additions & 6 deletions src/components/layout/ExportPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
<div class="dialog-footer">
<button class="btn secondary" @click="$emit('close')">关闭</button>
<template v-if="activeTab === 'image'">
<button v-if="authType === 'webdav'" class="btn primary" style="background:#0ea5e9;" :disabled="isGenerating || isUploading" @click="handleCloudExportImage">
<button v-if="authType === 'webdav'" class="btn primary btn-info" :disabled="isGenerating || isUploading" @click="handleCloudExportImage">
<Loader2 v-if="isUploading" :size="14" stroke-width="2" class="spin-icon" />
<CloudUpload v-else :size="14" stroke-width="2" />
{{ isUploading ? '上传中...' : '保存至云盘' }}
Expand All @@ -328,7 +328,7 @@
</button>
</template>
<template v-if="activeTab === 'excel'">
<button v-if="authType === 'webdav'" class="btn excel" style="background:var(--color-success);" :disabled="isExcelDownloading || isUploading" @click="handleCloudExportExcel">
<button v-if="authType === 'webdav'" class="btn excel" :disabled="isExcelDownloading || isUploading" @click="handleCloudExportExcel">
<Loader2 v-if="isUploading" :size="14" stroke-width="2" class="spin-icon" />
<CloudUpload v-else :size="14" stroke-width="2" />
{{ isUploading ? '上传中...' : '保存至云盘' }}
Expand Down Expand Up @@ -640,7 +640,7 @@ const excelPreviewHtml = computed(() => {

// 4. 开始构建 HTML 表格(含 Excel 式的外层标号 A, B, 1, 2)
let html = `<div style="font-family:Calibri,'Microsoft YaHei',sans-serif;display:flex;flex-direction:column;width:max-content;border:1px solid var(--color-border-strong);background:var(--color-surface);">`
html += `<div style="overflow:auto;background:#e6e6e6;max-height:65vh;">`
html += `<div style="overflow:auto;background:var(--color-bg-soft);max-height:65vh;">`
html += `<table style="border-collapse:collapse;font-family:inherit;margin:0;table-layout:fixed;">`

// `<colgroup>` 精确设置列宽
Expand Down Expand Up @@ -970,7 +970,7 @@ onBeforeUnmount(() => {
.export-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
background: var(--color-bg-overlay);
display: flex;
align-items: center;
justify-content: center;
Expand Down Expand Up @@ -1117,7 +1117,7 @@ onBeforeUnmount(() => {
.tag-list { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.tag-row {
display: flex; flex-direction: column; gap: 4px; padding: 6px 8px;
background: var(--color-bg-subtle); border-radius: 6px; border: 1px solid #eee;
background: var(--color-bg-subtle); border-radius: 6px; border: 1px solid var(--color-border);
}
.tag-dot { width: 12px; height: 12px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.15); flex-shrink: 0; }
.tag-input {
Expand Down Expand Up @@ -1205,7 +1205,7 @@ onBeforeUnmount(() => {
cursor: pointer;
position: relative;
flex-shrink: 0;
background-image: repeating-linear-gradient(45deg, var(--color-border-strong) 0, var(--color-border-strong) 1px, #f8f8f8 1px, #f8f8f8 5px);
background-image: repeating-linear-gradient(45deg, var(--color-border-strong) 0, var(--color-border-strong) 1px, var(--color-bg-soft) 1px, var(--color-bg-soft) 5px);
background-size: 6px 6px;
box-sizing: border-box;
}
Expand Down Expand Up @@ -1351,6 +1351,8 @@ onBeforeUnmount(() => {
.btn.secondary:hover { background: var(--color-border); }
.btn.primary { background: var(--color-primary); color: var(--color-text-inverse); }
.btn.primary:hover { background: var(--color-primary-hover); box-shadow: 0 3px 10px color-mix(in srgb, var(--color-primary) 30%, transparent); }
.btn.btn-info { background: var(--color-info); }
.btn.btn-info:hover { background: var(--color-info-hover); box-shadow: 0 3px 10px color-mix(in srgb, var(--color-info) 30%, transparent); }
.btn.excel { background: var(--color-success); color: var(--color-text-inverse); }
.btn.excel:hover { background: var(--color-success-hover); box-shadow: 0 3px 10px color-mix(in srgb, var(--color-success) 30%, transparent); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/SeatConfigDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ function handleConfirm() {
.seat-config-dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
background: var(--color-bg-overlay);
display: flex;
align-items: center;
justify-content: center;
Expand Down
13 changes: 8 additions & 5 deletions src/components/layout/SidebarPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ const AssignmentInlineReport = defineAsyncComponent(() => import('../rule/Assign
import { useAuth } from '@/composables/useAuth'
import { useUndo } from '@/composables/useUndo'
import { useCloudWorkspaceDialog } from '@/composables/useCloudWorkspaceDialog'
import { useAutoSave } from '@/composables/useAutoSave'

const { activeTab, mobileMenuOpen, setActiveTab, closeMobileMenu } = useSidebar()
const { seatConfig, updateConfig, clearAllSeats, seats, shiftSeats, getAvailableSeats } = useSeatChart()
Expand All @@ -543,6 +544,7 @@ const { scale, zoomIn, zoomOut, MIN_SCALE, MAX_SCALE, fitToViewport } = useZoom(
const { zones } = useZoneData()
const { recordBatch, createSnapshot } = useUndo()
const { openCloudLoad, openCloudSave } = useCloudWorkspaceDialog()
const { markSaved } = useAutoSave()
const showSeatConfigDialog = ref(false)
const totalSeats = computed(() => {
const config = seatConfig.value
Expand Down Expand Up @@ -724,6 +726,7 @@ const onExported = (payload) => {
const handleSaveWorkspace = () => {
const isSuccess = saveWorkspace()
if (isSuccess) {
markSaved()
success('工作区已成功保存到本地!')
} else {
error('工作区保存到本地失败,请查看控制台了解详情')
Expand Down Expand Up @@ -2273,12 +2276,12 @@ const formatLogTime = (timestamp) => {

.log-warning {
border-left-color: var(--color-warning);
background: var(--color-surface)bf0;
}




.log-error {
border-left-color: var(--color-danger);
background: var(--color-danger-bg-light);
}

.log-empty {
text-align: center;
Expand Down Expand Up @@ -2434,7 +2437,7 @@ const formatLogTime = (timestamp) => {
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
background: var(--color-bg-overlay);
z-index: 998;
backdrop-filter: blur(2px);
}
Expand Down
Loading
Loading