update:部分样式优化

This commit is contained in:
xiaozhu
2026-09-02 16:37:43 +08:00
parent 026cd085c6
commit d8e1eb9ca3
4 changed files with 81 additions and 40 deletions
+61 -38
View File
@@ -1,3 +1,5 @@
import { useMemo, useState } from "react";
import { ChevronDown } from "lucide-react";
import type { Employee, Role, Workspace } from "../../services/api"; import type { Employee, Role, Workspace } from "../../services/api";
import { import {
AppFormDialog, AppFormDialog,
@@ -59,6 +61,18 @@ export function UserFormDialog({
onClose: () => void; onClose: () => void;
onResetPassword?: () => void; onResetPassword?: () => void;
}) { }) {
const roleOptions = useMemo(
() => (editing ? roles : createRoleOptions),
[editing, roles, createRoleOptions],
);
const [roleMenuOpen, setRoleMenuOpen] = useState(false);
const roleDisabled = editing
? editing.user_id === currentUserId || roles.length === 0
: createRoleOptions.length === 0;
const selectedRole =
roleOptions.find((role) => role.role_code === form.role_code) ??
roleOptions[0];
return ( return (
<AppFormDialog <AppFormDialog
open={open} open={open}
@@ -122,45 +136,54 @@ export function UserFormDialog({
</label> </label>
<label className={formFieldClass}> <label className={formFieldClass}>
<span></span> <span></span>
<select <div className="relative">
className={formInputClass} <button
value={form.role_code} type="button"
disabled={ disabled={roleDisabled}
editing className={`${formInputClass} flex items-center justify-between gap-2 ${
? editing.user_id === currentUserId || roles.length === 0 roleDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
: createRoleOptions.length === 0 }`}
} onClick={() => !roleDisabled && setRoleMenuOpen((open) => !open)}
onChange={(event) => >
onChangeForm({ <span className="truncate text-left">
...form, {selectedRole ? selectedRole.role_name : "加载中…"}
role_code: event.target.value as UserFormState["role_code"], </span>
}) <ChevronDown
} size={15}
> className={`shrink-0 text-[#7890a8] transition-transform ${
{editing ? ( roleMenuOpen ? "rotate-180" : "rotate-0"
roles.length === 0 ? ( }`}
<option value="" disabled> />
</button>
</option> {roleMenuOpen && !roleDisabled && (
) : ( <div className="absolute left-0 right-0 z-30 mt-1 overflow-hidden rounded-md border border-[#d6e0e9] bg-white shadow-lg">
roles.map((role) => ( <div className="max-h-[180px] overflow-y-auto">
<option key={role.role_code} value={role.role_code}> {roleOptions.map((role) => {
{role.role_name} const isSelected = role.role_code === form.role_code;
</option> return (
)) <button
) key={role.role_code}
) : createRoleOptions.length === 0 ? ( type="button"
<option value="" disabled> className={`flex w-full items-center justify-between border-0 bg-white px-3 py-2 text-left text-[12px] text-[#2b4056] hover:bg-[#f3f7fb] ${
isSelected ? "bg-[#edf5ff] font-semibold text-[#176cc0]" : ""
</option> }`}
) : ( onClick={() => {
createRoleOptions.map((role) => ( onChangeForm({
<option key={role.role_code} value={role.role_code}> ...form,
{role.role_name} role_code: role.role_code as UserFormState["role_code"],
</option> });
)) setRoleMenuOpen(false);
}}
>
<span className="truncate">{role.role_name}</span>
{isSelected && <span className="ml-2 text-[11px]"></span>}
</button>
);
})}
</div>
</div>
)} )}
</select> </div>
</label> </label>
{!editing && ( {!editing && (
<CreateUserProjectField <CreateUserProjectField
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import { displayScriptPath } from "./scriptPath";
import { Info, Loader2Icon } from "lucide-react"; import { Info, Loader2Icon } from "lucide-react";
import type { ScriptItem } from "../../services/api"; import type { ScriptItem } from "../../services/api";
@@ -114,7 +115,7 @@ export function PythonEditor({
<i className="size-1.5 rounded-full bg-[#20b77d]" /> <i className="size-1.5 rounded-full bg-[#20b77d]" />
Workspace Python Editor Workspace Python Editor
</span> </span>
<span>{script.relative_path}</span> <span>{displayScriptPath(script.relative_path)}</span>
</div> </div>
<div className="relative min-h-0 flex-1 overflow-hidden"> <div className="relative min-h-0 flex-1 overflow-hidden">
<Editor <Editor
@@ -26,6 +26,7 @@ import {type PythonEditorBuffer, useScriptWorkspaceStore} from "./state/scriptWo
import { useAuth } from "../../context/AuthContext"; import { useAuth } from "../../context/AuthContext";
import { getScriptContent } from "../../services/api"; import { getScriptContent } from "../../services/api";
import {PythonEditor} from "~/features/platform/PythonEditor"; import {PythonEditor} from "~/features/platform/PythonEditor";
import { displayScriptPath } from "./scriptPath";
function fileIconTone(type: ScriptType | "data") { function fileIconTone(type: ScriptType | "data") {
if (type === "notebook") return "text-[#e15e50] bg-[#fff0ed]"; if (type === "notebook") return "text-[#e15e50] bg-[#fff0ed]";
@@ -761,7 +762,7 @@ export function ScriptWorkspace({
{script.script_name} {script.script_name}
</h2> </h2>
<p className="m-0 font-mono text-[10px] text-[#8997a6]"> <p className="m-0 font-mono text-[10px] text-[#8997a6]">
{script.relative_path} {displayScriptPath(script.relative_path)}
</p> </p>
</div> </div>
<button <button
@@ -0,0 +1,16 @@
/**
* Storage metadata keeps workspace files under
* `workspace/<owner-user-id>/<logical-path>`. That owner segment is an
* implementation detail and should never be presented as part of a script's
* user-facing path.
*/
export function displayScriptPath(relativePath: string): string {
const normalized = relativePath.replaceAll("\\", "/");
const parts = normalized.split("/");
if (parts[0] === "workspace" && parts[1] && parts.length > 2) {
return parts.slice(2).join("/");
}
return normalized;
}