1
0

12 Коммиты cfcf5c2070 ... 0c6ea0f92a

Автор SHA1 Сообщение Дата
  yangyi 0c6ea0f92a fix(editor): MonacoEditor 容器改为 border-box,消除编辑器下方横向滚动条 2 недель назад
  yangyi c60bae92f8 docs: 页脚毕业设计信息增加分隔符 2 недель назад
  yangyi 1938ce44c8 feat: 在用户布局页脚展示毕业设计信息 2 недель назад
  yangyi 569c1e8176 style(theme): 全站主题色统一为 Element Plus 默认主题 2 недель назад
  yangyi 30aa18bf25 fix(admin): 操作列加宽并禁止换行,保证所有按钮单行显示 2 недель назад
  yangyi 2329bd9a66 fix(layout): 个人信息下拉项占位提示、注册按钮跳转注册页签、后台移除无效注册按钮 2 недель назад
  yangyi 445a0f9304 fix(contest-config): 打开弹窗自动加载候选题目、保存按钮受加载状态约束、难度显示中文 2 недель назад
  yangyi 41330c6ae2 fix(contest-manage): 报名按钮改为报名名单、编辑预填描述、密码/描述留空不修改、统一成功码 2 недель назад
  yangyi a5ababff43 fix(category-manage): 统一成功码判断、弹窗关闭重置校验 2 недель назад
  yangyi 05de9b18ba fix(problem-manage): 文案与弹窗内容对齐、修复详情标题闪现、统一成功码判断、弹窗关闭重置校验 2 недель назад
  yangyi 7bd519a503 fix(user-manage): 状态筛选默认全部、移除角色长度校验、统一全角问号、清理未用导入 2 недель назад
  yangyi 43eaf06db4 style: 后台管理表单对话框统一采用标签在上、输入组件在下的布局 2 недель назад

+ 1 - 0
src/components/MonacoEditor.vue

@@ -89,6 +89,7 @@ onBeforeUnmount(() => {
 
 
 <style scoped>
 <style scoped>
 .monaco-editor {
 .monaco-editor {
+  box-sizing: border-box;
   width: 100%;
   width: 100%;
   border: 1px solid var(--el-border-color);
   border: 1px solid var(--el-border-color);
   border-radius: 4px;
   border-radius: 4px;

+ 9 - 6
src/layout/AdminLayout.vue

@@ -4,6 +4,7 @@ import {computed, type ComputedRef, ref} from "vue";
 import type {RouteRecordRaw} from "vue-router";
 import type {RouteRecordRaw} from "vue-router";
 import router, {adminRoutes} from "../router";
 import router, {adminRoutes} from "../router";
 import {Expand, Fold, UserFilled} from "@element-plus/icons-vue";
 import {Expand, Fold, UserFilled} from "@element-plus/icons-vue";
+import {ElMessage} from "element-plus";
 
 
 const title = ref<string>("分布式OJ系统");
 const title = ref<string>("分布式OJ系统");
 const loginUserStore = useLoginUserStore()
 const loginUserStore = useLoginUserStore()
@@ -15,6 +16,9 @@ const logoutHandler = () => {
   loginUserStore.logoutUser()
   loginUserStore.logoutUser()
   router.push('/problems')
   router.push('/problems')
 }
 }
+const handleProfile = () => {
+  ElMessage.info('功能开发中,敬请期待')
+}
 const menus:ComputedRef<RouteRecordRaw[] | undefined> = computed(()=>{
 const menus:ComputedRef<RouteRecordRaw[] | undefined> = computed(()=>{
   const role = loginUserStore.loginUser.user?.role
   const role = loginUserStore.loginUser.user?.role
   return adminRoutes[0].children?.filter((child) => {
   return adminRoutes[0].children?.filter((child) => {
@@ -43,7 +47,7 @@ const isCollapse = ref(false)
             </el-button>
             </el-button>
             <template #dropdown>
             <template #dropdown>
               <el-dropdown-menu>
               <el-dropdown-menu>
-                <el-dropdown-item>个人信息</el-dropdown-item>
+                <el-dropdown-item @click="handleProfile">个人信息</el-dropdown-item>
                 <el-dropdown-item divided @click="logoutHandler">退出登录</el-dropdown-item>
                 <el-dropdown-item divided @click="logoutHandler">退出登录</el-dropdown-item>
               </el-dropdown-menu>
               </el-dropdown-menu>
             </template>
             </template>
@@ -51,7 +55,6 @@ const isCollapse = ref(false)
         </template>
         </template>
         <template v-else>
         <template v-else>
           <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
           <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
-          <el-button size="small" @click="loginHandler">注册</el-button>
         </template>
         </template>
       </div>
       </div>
     </el-header>
     </el-header>
@@ -95,7 +98,7 @@ const isCollapse = ref(false)
   justify-content: space-between;
   justify-content: space-between;
   padding: 0 20px;
   padding: 0 20px;
   margin-bottom: 10px;
   margin-bottom: 10px;
-  border-bottom: 1px solid
+  border-bottom: 1px solid var(--el-border-color);
 }
 }
 .header-left {
 .header-left {
   display: flex;
   display: flex;
@@ -118,7 +121,7 @@ const isCollapse = ref(false)
 }
 }
 .username {
 .username {
   font-size: 14px;
   font-size: 14px;
-  color: #606266;
+  color: var(--el-text-color-regular);
 }
 }
 
 
 .user-btn {
 .user-btn {
@@ -130,12 +133,12 @@ const isCollapse = ref(false)
 }
 }
 
 
 .user-btn:hover {
 .user-btn:hover {
-  color: #db2777;
+  color: var(--el-color-primary);
 }
 }
 
 
 .user-avatar {
 .user-avatar {
   font-size: 20px;
   font-size: 20px;
-  color: #be185d;
+  color: var(--el-color-primary);
 }
 }
 
 
 .menu{
 .menu{

+ 29 - 11
src/layout/UserLayout.vue

@@ -36,7 +36,7 @@
               </el-button>
               </el-button>
               <template #dropdown>
               <template #dropdown>
                 <el-dropdown-menu>
                 <el-dropdown-menu>
-                  <el-dropdown-item>个人信息</el-dropdown-item>
+                  <el-dropdown-item @click="handleProfile">个人信息</el-dropdown-item>
                   <el-dropdown-item divided @click="logoutHandler">退出登录</el-dropdown-item>
                   <el-dropdown-item divided @click="logoutHandler">退出登录</el-dropdown-item>
                 </el-dropdown-menu>
                 </el-dropdown-menu>
               </template>
               </template>
@@ -44,7 +44,7 @@
           </template>
           </template>
           <template v-else>
           <template v-else>
             <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
             <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
-            <el-button size="small" @click="router.push('/login')">注册</el-button>
+            <el-button size="small" @click="router.push({ path: '/login', query: { tab: 'register' } })">注册</el-button>
           </template>
           </template>
         </div>
         </div>
       </el-header>
       </el-header>
@@ -53,7 +53,7 @@
         <router-view/>
         <router-view/>
       </el-main>
       </el-main>
 
 
-      <el-footer>Footer</el-footer>
+      <el-footer class="footer">广东外语外贸大学南国商学院-25计算机科学与技术专升本4班-黄略-毕业设计-基于 Spring Cloud 的分布式在线判题系统(分布式 OJ 系统)</el-footer>
     </el-container>
     </el-container>
   </div>
   </div>
 </template>
 </template>
@@ -61,6 +61,7 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import {computed} from 'vue'
 import {computed} from 'vue'
 import {useRoute} from 'vue-router'
 import {useRoute} from 'vue-router'
+import {ElMessage} from 'element-plus'
 import {useLoginUserStore, useMetaStore} from "../store";
 import {useLoginUserStore, useMetaStore} from "../store";
 import router, {userRoutes} from "../router";
 import router, {userRoutes} from "../router";
 import {
 import {
@@ -86,6 +87,9 @@ const logoutHandler = () => {
   loginUserStore.logoutUser()
   loginUserStore.logoutUser()
   router.push('/problems')
   router.push('/problems')
 }
 }
+const handleProfile = () => {
+  ElMessage.info('功能开发中,敬请期待')
+}
 </script>
 </script>
 <style scoped>
 <style scoped>
 .user-layout {
 .user-layout {
@@ -96,12 +100,26 @@ const logoutHandler = () => {
 el-main{
 el-main{
   flex: 1;
   flex: 1;
 }
 }
+.footer {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: auto;
+  min-height: 60px;
+  padding: 16px;
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
+  text-align: center;
+  line-height: 1.6;
+  border-top: 1px solid var(--el-border-color-lighter);
+  word-break: break-word;
+}
 .header {
 .header {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: space-between;
   justify-content: space-between;
   padding: 0 20px;
   padding: 0 20px;
-  border-bottom: 1px solid;
+  border-bottom: 1px solid var(--el-border-color);
 }
 }
 .header-left {
 .header-left {
   display: flex;
   display: flex;
@@ -130,17 +148,17 @@ el-main{
   height: auto;
   height: auto;
   padding: 2px 4px;
   padding: 2px 4px;
   font-size: 14px;
   font-size: 14px;
-  color: #606266;
+  color: var(--el-text-color-regular);
   text-decoration: none;
   text-decoration: none;
   border-bottom: 2px solid transparent;
   border-bottom: 2px solid transparent;
   transition: color 0.2s, border-color 0.2s;
   transition: color 0.2s, border-color 0.2s;
 }
 }
 .nav-link:hover {
 .nav-link:hover {
-  color: #db2777;
+  color: var(--el-color-primary);
 }
 }
 .nav-link.active {
 .nav-link.active {
-  color: #db2777;
-  border-bottom-color: #db2777;
+  color: var(--el-color-primary);
+  border-bottom-color: var(--el-color-primary);
 }
 }
 .header-right {
 .header-right {
   display: flex;
   display: flex;
@@ -149,7 +167,7 @@ el-main{
 }
 }
 .username {
 .username {
   font-size: 14px;
   font-size: 14px;
-  color: #606266;
+  color: var(--el-text-color-regular);
 }
 }
 
 
 .user-btn {
 .user-btn {
@@ -161,11 +179,11 @@ el-main{
 }
 }
 
 
 .user-btn:hover {
 .user-btn:hover {
-  color: #db2777;
+  color: var(--el-color-primary);
 }
 }
 
 
 .user-avatar {
 .user-avatar {
   font-size: 20px;
   font-size: 20px;
-  color: #be185d;
+  color: var(--el-color-primary);
 }
 }
 </style>
 </style>

+ 24 - 22
src/view/LoginView.vue

@@ -137,7 +137,7 @@ const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 const loginUserStore = useLoginUserStore()
 const loginUserStore = useLoginUserStore()
 
 
-const activeTab = ref('login')
+const activeTab = ref(route.query.tab === 'register' ? 'register' : 'login')
 
 
 const loginFormRef = ref<FormInstance>()
 const loginFormRef = ref<FormInstance>()
 const registerFormRef = ref<FormInstance>()
 const registerFormRef = ref<FormInstance>()
@@ -254,7 +254,12 @@ const handleRegister = async () => {
   justify-content: center;
   justify-content: center;
   align-items: center;
   align-items: center;
   min-height: 100vh;
   min-height: 100vh;
-  background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 50%, #fbcfe8 100%);
+  background: linear-gradient(
+    135deg,
+    var(--el-color-primary-light-9) 0%,
+    var(--el-color-primary-light-8) 50%,
+    var(--el-color-primary-light-7) 100%
+  );
   position: relative;
   position: relative;
   overflow: hidden;
   overflow: hidden;
 }
 }
@@ -274,7 +279,7 @@ const handleRegister = async () => {
 .circle-1 {
 .circle-1 {
   width: 400px;
   width: 400px;
   height: 400px;
   height: 400px;
-  background: #db2777;
+  background: var(--el-color-primary);
   top: -100px;
   top: -100px;
   right: -100px;
   right: -100px;
   animation: float 20s ease-in-out infinite;
   animation: float 20s ease-in-out infinite;
@@ -283,7 +288,7 @@ const handleRegister = async () => {
 .circle-2 {
 .circle-2 {
   width: 300px;
   width: 300px;
   height: 300px;
   height: 300px;
-  background: #f472b6;
+  background: var(--el-color-primary-light-3);
   bottom: -80px;
   bottom: -80px;
   left: -80px;
   left: -80px;
   animation: float 15s ease-in-out infinite reverse;
   animation: float 15s ease-in-out infinite reverse;
@@ -292,7 +297,7 @@ const handleRegister = async () => {
 .circle-3 {
 .circle-3 {
   width: 200px;
   width: 200px;
   height: 200px;
   height: 200px;
-  background: #ca8a04;
+  background: var(--el-color-warning);
   top: 50%;
   top: 50%;
   left: 60%;
   left: 60%;
   animation: float 18s ease-in-out infinite;
   animation: float 18s ease-in-out infinite;
@@ -324,7 +329,7 @@ const handleRegister = async () => {
 
 
 .auth-card {
 .auth-card {
   border-radius: 16px;
   border-radius: 16px;
-  border: 1px solid rgba(219, 39, 119, 0.1);
+  border: 1px solid color-mix(in srgb, var(--el-color-primary) 10%, transparent);
   background: rgba(255, 255, 255, 0.9);
   background: rgba(255, 255, 255, 0.9);
   backdrop-filter: blur(10px);
   backdrop-filter: blur(10px);
   padding: 8px;
   padding: 8px;
@@ -334,14 +339,14 @@ const handleRegister = async () => {
   position: absolute;
   position: absolute;
   top: 16px;
   top: 16px;
   left: 16px;
   left: 16px;
-  color: #be185d;
+  color: var(--el-color-primary);
   font-size: 14px;
   font-size: 14px;
   cursor: pointer;
   cursor: pointer;
   transition: color 0.2s;
   transition: color 0.2s;
 }
 }
 
 
 .back-btn:hover {
 .back-btn:hover {
-  color: #db2777;
+  color: var(--el-color-primary-dark-2);
 }
 }
 
 
 .back-btn .el-icon {
 .back-btn .el-icon {
@@ -357,14 +362,14 @@ const handleRegister = async () => {
   margin: 0 0 8px;
   margin: 0 0 8px;
   font-size: 28px;
   font-size: 28px;
   font-weight: 600;
   font-weight: 600;
-  color: #831843;
+  color: var(--el-text-color-primary);
   letter-spacing: 2px;
   letter-spacing: 2px;
 }
 }
 
 
 .auth-subtitle {
 .auth-subtitle {
   margin: 0;
   margin: 0;
   font-size: 14px;
   font-size: 14px;
-  color: #be185d;
+  color: var(--el-text-color-secondary);
 }
 }
 
 
 .auth-tabs {
 .auth-tabs {
@@ -378,22 +383,22 @@ const handleRegister = async () => {
 :deep(.auth-tabs .el-tabs__item) {
 :deep(.auth-tabs .el-tabs__item) {
   font-size: 16px;
   font-size: 16px;
   font-weight: 500;
   font-weight: 500;
-  color: #831843;
+  color: var(--el-text-color-regular);
   transition: color 0.2s;
   transition: color 0.2s;
 }
 }
 
 
 :deep(.auth-tabs .el-tabs__item.is-active) {
 :deep(.auth-tabs .el-tabs__item.is-active) {
-  color: #db2777;
+  color: var(--el-color-primary);
 }
 }
 
 
 :deep(.auth-tabs .el-tabs__active-bar) {
 :deep(.auth-tabs .el-tabs__active-bar) {
-  background-color: #db2777;
+  background-color: var(--el-color-primary);
   height: 3px;
   height: 3px;
   border-radius: 3px;
   border-radius: 3px;
 }
 }
 
 
 :deep(.auth-tabs .el-tabs__nav-wrap::after) {
 :deep(.auth-tabs .el-tabs__nav-wrap::after) {
-  background-color: rgba(219, 39, 119, 0.15);
+  background-color: color-mix(in srgb, var(--el-color-primary) 15%, transparent);
   height: 1px;
   height: 1px;
 }
 }
 
 
@@ -404,20 +409,20 @@ const handleRegister = async () => {
 :deep(.el-input__wrapper) {
 :deep(.el-input__wrapper) {
   border-radius: 10px;
   border-radius: 10px;
   padding: 8px 12px;
   padding: 8px 12px;
-  box-shadow: 0 0 0 1px rgba(219, 39, 119, 0.15) inset;
+  box-shadow: 0 0 0 1px color-mix(in srgb, var(--el-color-primary) 15%, transparent) inset;
   transition: box-shadow 0.2s;
   transition: box-shadow 0.2s;
 }
 }
 
 
 :deep(.el-input__wrapper:hover) {
 :deep(.el-input__wrapper:hover) {
-  box-shadow: 0 0 0 1px rgba(219, 39, 119, 0.3) inset;
+  box-shadow: 0 0 0 1px color-mix(in srgb, var(--el-color-primary) 30%, transparent) inset;
 }
 }
 
 
 :deep(.el-input__wrapper.is-focus) {
 :deep(.el-input__wrapper.is-focus) {
-  box-shadow: 0 0 0 1px #db2777 inset;
+  box-shadow: 0 0 0 1px var(--el-color-primary) inset;
 }
 }
 
 
 :deep(.el-input__prefix) {
 :deep(.el-input__prefix) {
-  color: #db2777;
+  color: var(--el-color-primary);
   margin-right: 8px;
   margin-right: 8px;
 }
 }
 
 
@@ -427,15 +432,12 @@ const handleRegister = async () => {
   font-size: 16px;
   font-size: 16px;
   font-weight: 500;
   font-weight: 500;
   letter-spacing: 4px;
   letter-spacing: 4px;
-  background: linear-gradient(135deg, #db2777 0%, #be185d 100%);
-  border: none;
   transition: all 0.2s;
   transition: all 0.2s;
 }
 }
 
 
 .auth-submit:hover {
 .auth-submit:hover {
-  background: linear-gradient(135deg, #be185d 0%, #9d174d 100%);
   transform: translateY(-1px);
   transform: translateY(-1px);
-  box-shadow: 0 4px 12px rgba(219, 39, 119, 0.3);
+  box-shadow: 0 4px 12px color-mix(in srgb, var(--el-color-primary) 30%, transparent);
 }
 }
 
 
 .auth-submit:active {
 .auth-submit:active {

+ 14 - 6
src/view/admin/CategoryManageView.vue

@@ -30,7 +30,7 @@ const fetchData = async () => {
   loading.value = true
   loading.value = true
   try {
   try {
     const res = await api.list()
     const res = await api.list()
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       tableData.value = res.data || []
       tableData.value = res.data || []
     }
     }
   } catch {
   } catch {
@@ -66,7 +66,7 @@ const handleSave = async () => {
       description: categoryForm.description || undefined
       description: categoryForm.description || undefined
     }
     }
     const res = isEdit.value ? await api.edit3(data) : await api.add3(data)
     const res = isEdit.value ? await api.edit3(data) : await api.add3(data)
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       dialogVisible.value = false
       dialogVisible.value = false
       fetchData()
       fetchData()
@@ -81,7 +81,7 @@ const handleDelete = (row: CategoryVo) => {
     .then(async () => {
     .then(async () => {
       try {
       try {
         const res = await api._delete({ id: row.id! })
         const res = await api._delete({ id: row.id! })
-        if (res?.code === 200) {
+        if (res?.code === 0 || res?.code === 200) {
           ElMessage.success('删除成功')
           ElMessage.success('删除成功')
           fetchData()
           fetchData()
         }
         }
@@ -91,6 +91,10 @@ const handleDelete = (row: CategoryVo) => {
     .catch(() => {})
     .catch(() => {})
 }
 }
 
 
+const handleDialogClosed = () => {
+  formRef.value?.resetFields()
+}
+
 onMounted(fetchData)
 onMounted(fetchData)
 </script>
 </script>
 
 
@@ -110,7 +114,7 @@ onMounted(fetchData)
         <el-table-column prop="id" label="ID" width="80" align="center" />
         <el-table-column prop="id" label="ID" width="80" align="center" />
         <el-table-column prop="categoryName" label="分类名称" min-width="180" />
         <el-table-column prop="categoryName" label="分类名称" min-width="180" />
         <el-table-column prop="description" label="描述" min-width="240" show-overflow-tooltip />
         <el-table-column prop="description" label="描述" min-width="240" show-overflow-tooltip />
-        <el-table-column label="操作" width="160" align="center" fixed="right">
+        <el-table-column label="操作" width="160" align="center" fixed="right" class-name="op-column">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button type="primary" link @click="handleEdit(row)">
             <el-button type="primary" link @click="handleEdit(row)">
               <el-icon><Edit /></el-icon>编辑
               <el-icon><Edit /></el-icon>编辑
@@ -123,8 +127,8 @@ onMounted(fetchData)
       </el-table>
       </el-table>
     </el-card>
     </el-card>
 
 
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="480px" draggable>
-      <el-form ref="formRef" :model="categoryForm" :rules="formRules" label-width="90px">
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="480px" draggable @closed="handleDialogClosed">
+      <el-form ref="formRef" :model="categoryForm" :rules="formRules" label-position="top">
         <el-form-item label="分类名称" prop="categoryName">
         <el-form-item label="分类名称" prop="categoryName">
           <el-input v-model="categoryForm.categoryName" placeholder="请输入分类名称" />
           <el-input v-model="categoryForm.categoryName" placeholder="请输入分类名称" />
         </el-form-item>
         </el-form-item>
@@ -152,4 +156,8 @@ onMounted(fetchData)
 .table-card {
 .table-card {
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }
+
+:deep(.el-table .op-column .cell) {
+  white-space: nowrap;
+}
 </style>
 </style>

+ 51 - 14
src/view/admin/ContestManageView.vue

@@ -52,6 +52,14 @@ const statusTag = (status?: ContestVo['status']) => {
 const statusLabel = (status?: ContestVo['status']) =>
 const statusLabel = (status?: ContestVo['status']) =>
   statusOptions.find((s) => s.value === status)?.label || status || '-'
   statusOptions.find((s) => s.value === status)?.label || status || '-'
 
 
+const difficultyOptions = [
+  { label: '简单', value: 'easy' },
+  { label: '中等', value: 'medium' },
+  { label: '困难', value: 'hard' }
+]
+
+const difficultyLabel = (d?: string) => difficultyOptions.find((i) => i.value === d)?.label || d || '-'
+
 // ---------- 搜索 ----------
 // ---------- 搜索 ----------
 const searchForm = reactive({
 const searchForm = reactive({
   title: '',
   title: '',
@@ -126,7 +134,7 @@ const formRules: FormRules = {
   password: [
   password: [
     {
     {
       validator: (_rule, value, callback) => {
       validator: (_rule, value, callback) => {
-        if (contestForm.type === 'password' && !value) {
+        if (contestForm.type === 'password' && !value && !contestForm.id) {
           callback(new Error('密码型比赛请输入访问密码'))
           callback(new Error('密码型比赛请输入访问密码'))
         } else {
         } else {
           callback()
           callback()
@@ -161,6 +169,17 @@ const handleEdit = (row: ContestVo) => {
   contestForm.startDatetime = row.startDatetime || ''
   contestForm.startDatetime = row.startDatetime || ''
   contestForm.endDatetime = row.endDatetime || ''
   contestForm.endDatetime = row.endDatetime || ''
   dialogVisible.value = true
   dialogVisible.value = true
+  if (row.id !== undefined && row.type !== 'password') {
+    api
+      .getDetail2(row.id)
+      .then((res) => {
+        const d = res?.data
+        if (d && contestForm.id === row.id) {
+          contestForm.description = d.description || ''
+        }
+      })
+      .catch(() => {})
+  }
 }
 }
 
 
 const handleSave = async () => {
 const handleSave = async () => {
@@ -176,12 +195,12 @@ const handleSave = async () => {
       title: contestForm.title!,
       title: contestForm.title!,
       description: contestForm.description || undefined,
       description: contestForm.description || undefined,
       type: contestForm.type as ContestDto['type'],
       type: contestForm.type as ContestDto['type'],
-      password: contestForm.type === 'password' ? contestForm.password : undefined,
+      password: contestForm.type === 'password' && contestForm.password ? contestForm.password : undefined,
       startDatetime: contestForm.startDatetime!,
       startDatetime: contestForm.startDatetime!,
       endDatetime: contestForm.endDatetime!
       endDatetime: contestForm.endDatetime!
     }
     }
     const res = isEdit.value ? await api.edit2(data) : await api.add2(data)
     const res = isEdit.value ? await api.edit2(data) : await api.add2(data)
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       dialogVisible.value = false
       dialogVisible.value = false
       fetchData()
       fetchData()
@@ -196,7 +215,7 @@ const handleDelete = (row: ContestVo) => {
     .then(async () => {
     .then(async () => {
       try {
       try {
         const res = await api.deleteByIds1({ ids: [row.id!] })
         const res = await api.deleteByIds1({ ids: [row.id!] })
-        if (res?.code === 200) {
+        if (res?.code === 0 || res?.code === 200) {
           ElMessage.success('删除成功')
           ElMessage.success('删除成功')
           fetchData()
           fetchData()
         }
         }
@@ -206,6 +225,10 @@ const handleDelete = (row: ContestVo) => {
     .catch(() => {})
     .catch(() => {})
 }
 }
 
 
+const handleDialogClosed = () => {
+  formRef.value?.resetFields()
+}
+
 // ---------- 题目配置 ----------
 // ---------- 题目配置 ----------
 const configDialogVisible = ref(false)
 const configDialogVisible = ref(false)
 const configContestId = ref<number | undefined>(undefined)
 const configContestId = ref<number | undefined>(undefined)
@@ -265,7 +288,7 @@ const loadConfig = async () => {
   }
   }
   try {
   try {
     const res = await api.getDetail2(configContestId.value, configPassword.value ? { password: configPassword.value } : undefined)
     const res = await api.getDetail2(configContestId.value, configPassword.value ? { password: configPassword.value } : undefined)
-    if (res?.code === 200 && res.data) {
+    if ((res?.code === 0 || res?.code === 200) && res.data) {
       const d = res.data
       const d = res.data
       if (d.type === 'password' && !d.creatorUsername) {
       if (d.type === 'password' && !d.creatorUsername) {
         ElMessage.error('比赛密码错误')
         ElMessage.error('比赛密码错误')
@@ -350,8 +373,11 @@ const openConfig = (row: ContestVo) => {
   configLoaded.value = false
   configLoaded.value = false
   existingProblems.value = []
   existingProblems.value = []
   configProblems.value = []
   configProblems.value = []
+  candSearch.title = ''
+  candPagination.pageNum = 1
   configDialogVisible.value = true
   configDialogVisible.value = true
   loadConfig()
   loadConfig()
+  loadCandidates()
 }
 }
 
 
 // ---------- 报名名单 ----------
 // ---------- 报名名单 ----------
@@ -370,7 +396,7 @@ const openRegistrations = (row: ContestVo) => {
   api
   api
     .queryRegistrations(id)
     .queryRegistrations(id)
     .then((res) => {
     .then((res) => {
-      if (res?.code === 200) {
+      if (res?.code === 0 || res?.code === 200) {
         regTable.value = res.data || []
         regTable.value = res.data || []
       }
       }
     })
     })
@@ -435,7 +461,7 @@ onMounted(fetchData)
         <el-table-column label="结束时间" width="150" align="center">
         <el-table-column label="结束时间" width="150" align="center">
           <template #default="{ row }">{{ formatTime(row.endDatetime) }}</template>
           <template #default="{ row }">{{ formatTime(row.endDatetime) }}</template>
         </el-table-column>
         </el-table-column>
-        <el-table-column label="操作" width="250" align="center" fixed="right">
+        <el-table-column label="操作" width="340" align="center" fixed="right" class-name="op-column">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button type="primary" link @click="handleEdit(row)">
             <el-button type="primary" link @click="handleEdit(row)">
               <el-icon><Edit /></el-icon>编辑
               <el-icon><Edit /></el-icon>编辑
@@ -444,7 +470,7 @@ onMounted(fetchData)
               <el-icon><View /></el-icon>配置题目
               <el-icon><View /></el-icon>配置题目
             </el-button>
             </el-button>
             <el-button type="primary" link @click="openRegistrations(row)">
             <el-button type="primary" link @click="openRegistrations(row)">
-              <el-icon><UserFilled /></el-icon>报名
+              <el-icon><UserFilled /></el-icon>报名名单
             </el-button>
             </el-button>
             <el-button v-if="isAdmin" type="danger" link @click="handleDelete(row)">
             <el-button v-if="isAdmin" type="danger" link @click="handleDelete(row)">
               <el-icon><Delete /></el-icon>删除
               <el-icon><Delete /></el-icon>删除
@@ -467,13 +493,14 @@ onMounted(fetchData)
     </el-card>
     </el-card>
 
 
     <!-- 新增/编辑比赛 -->
     <!-- 新增/编辑比赛 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px" draggable>
-      <el-form ref="formRef" :model="contestForm" :rules="formRules" label-width="100px">
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px" draggable @closed="handleDialogClosed">
+      <el-form ref="formRef" :model="contestForm" :rules="formRules" label-position="top">
         <el-form-item label="标题" prop="title">
         <el-form-item label="标题" prop="title">
           <el-input v-model="contestForm.title" placeholder="请输入比赛标题" />
           <el-input v-model="contestForm.title" placeholder="请输入比赛标题" />
         </el-form-item>
         </el-form-item>
         <el-form-item label="描述">
         <el-form-item label="描述">
           <el-input v-model="contestForm.description" type="textarea" :rows="3" placeholder="请输入比赛描述" />
           <el-input v-model="contestForm.description" type="textarea" :rows="3" placeholder="请输入比赛描述" />
+          <span v-if="isEdit" class="tip">留空则不修改描述</span>
         </el-form-item>
         </el-form-item>
         <el-form-item label="类型" prop="type">
         <el-form-item label="类型" prop="type">
           <el-select v-model="contestForm.type" style="width: 200px">
           <el-select v-model="contestForm.type" style="width: 200px">
@@ -482,6 +509,7 @@ onMounted(fetchData)
         </el-form-item>
         </el-form-item>
         <el-form-item v-if="contestForm.type === 'password'" label="访问密码" prop="password">
         <el-form-item v-if="contestForm.type === 'password'" label="访问密码" prop="password">
           <el-input v-model="contestForm.password" type="password" show-password placeholder="请输入访问密码" />
           <el-input v-model="contestForm.password" type="password" show-password placeholder="请输入访问密码" />
+          <span v-if="isEdit" class="tip">留空则不修改密码</span>
         </el-form-item>
         </el-form-item>
         <el-form-item label="开始时间" prop="startDatetime">
         <el-form-item label="开始时间" prop="startDatetime">
           <el-date-picker
           <el-date-picker
@@ -540,7 +568,7 @@ onMounted(fetchData)
             <el-table-column prop="id" label="ID" width="60" align="center" />
             <el-table-column prop="id" label="ID" width="60" align="center" />
             <el-table-column prop="title" label="标题" min-width="150" show-overflow-tooltip />
             <el-table-column prop="title" label="标题" min-width="150" show-overflow-tooltip />
             <el-table-column prop="difficulty" label="难度" width="80" align="center">
             <el-table-column prop="difficulty" label="难度" width="80" align="center">
-              <template #default="{ row }">{{ row.difficulty || '-' }}</template>
+              <template #default="{ row }">{{ difficultyLabel(row.difficulty) }}</template>
             </el-table-column>
             </el-table-column>
           </el-table>
           </el-table>
           <div class="pagination">
           <div class="pagination">
@@ -559,7 +587,6 @@ onMounted(fetchData)
         <div class="config-panel">
         <div class="config-panel">
           <div class="config-header">
           <div class="config-header">
             <span class="card-title">已选题目({{ configProblems.length }})</span>
             <span class="card-title">已选题目({{ configProblems.length }})</span>
-            <el-button v-if="configLoaded" type="primary" size="small" :loading="configSaving" @click="saveConfig">保存配置</el-button>
           </div>
           </div>
           <el-table v-if="configProblems.length" :data="configProblems" border stripe size="small" height="420">
           <el-table v-if="configProblems.length" :data="configProblems" border stripe size="small" height="420">
             <el-table-column label="题号" width="90" align="center">
             <el-table-column label="题号" width="90" align="center">
@@ -573,7 +600,7 @@ onMounted(fetchData)
                 <el-input-number v-model="row.score" :min="0" :max="1000" size="small" controls-position="right" style="width: 90px" />
                 <el-input-number v-model="row.score" :min="0" :max="1000" size="small" controls-position="right" style="width: 90px" />
               </template>
               </template>
             </el-table-column>
             </el-table-column>
-            <el-table-column label="操作" width="60" align="center">
+            <el-table-column label="操作" width="60" align="center" class-name="op-column">
               <template #default="{ row }">
               <template #default="{ row }">
                 <el-button type="danger" link size="small" @click="removeConfigured(row.problemId)">移除</el-button>
                 <el-button type="danger" link size="small" @click="removeConfigured(row.problemId)">移除</el-button>
               </template>
               </template>
@@ -584,7 +611,7 @@ onMounted(fetchData)
       </div>
       </div>
       <template #footer>
       <template #footer>
         <el-button @click="configDialogVisible = false">关闭</el-button>
         <el-button @click="configDialogVisible = false">关闭</el-button>
-        <el-button type="primary" :loading="configSaving" @click="saveConfig">保存配置</el-button>
+        <el-button type="primary" :disabled="!configLoaded" :loading="configSaving" @click="saveConfig">保存配置</el-button>
       </template>
       </template>
     </el-dialog>
     </el-dialog>
 
 
@@ -620,6 +647,10 @@ onMounted(fetchData)
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }
 
 
+:deep(.el-table .op-column .cell) {
+  white-space: nowrap;
+}
+
 .pagination {
 .pagination {
   display: flex;
   display: flex;
   justify-content: flex-end;
   justify-content: flex-end;
@@ -660,4 +691,10 @@ onMounted(fetchData)
 .card-title {
 .card-title {
   font-weight: 600;
   font-weight: 600;
 }
 }
+
+.tip {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
 </style>
 </style>

+ 41 - 24
src/view/admin/ProblemManageView.vue

@@ -209,7 +209,7 @@ const handleSave = async () => {
       categoryIds: problemForm.categoryIds || []
       categoryIds: problemForm.categoryIds || []
     }
     }
     const res = isEdit.value ? await api.edit1(data) : await api.add1(data)
     const res = isEdit.value ? await api.edit1(data) : await api.add1(data)
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       dialogVisible.value = false
       dialogVisible.value = false
       fetchData()
       fetchData()
@@ -224,7 +224,7 @@ const handleDelete = (row: ProblemVo) => {
     .then(async () => {
     .then(async () => {
       try {
       try {
         const res = await api.deleteByIds({ ids: [row.id!] })
         const res = await api.deleteByIds({ ids: [row.id!] })
-        if (res?.code === 200) {
+        if (res?.code === 0 || res?.code === 200) {
           ElMessage.success('删除成功')
           ElMessage.success('删除成功')
           fetchData()
           fetchData()
         }
         }
@@ -244,7 +244,7 @@ const handleBatchDelete = () => {
       try {
       try {
         const ids = selectedRows.value.map((r) => r.id).filter((id): id is number => id !== undefined)
         const ids = selectedRows.value.map((r) => r.id).filter((id): id is number => id !== undefined)
         const res = await api.deleteByIds({ ids })
         const res = await api.deleteByIds({ ids })
-        if (res?.code === 200) {
+        if (res?.code === 0 || res?.code === 200) {
           ElMessage.success('删除成功')
           ElMessage.success('删除成功')
           selectedRows.value = []
           selectedRows.value = []
           fetchData()
           fetchData()
@@ -267,9 +267,10 @@ const openDetail = (row: ProblemVo) => {
   const id = row.id
   const id = row.id
   if (id === undefined) return
   if (id === undefined) return
   currentProblemId.value = id
   currentProblemId.value = id
+  detail.value = { title: row.title }
   detailTab.value = 'cases'
   detailTab.value = 'cases'
   detailVisible.value = true
   detailVisible.value = true
-  loadDetail(id)
+  loadDetail(id).catch(() => {})
 }
 }
 
 
 // 样例
 // 样例
@@ -306,10 +307,10 @@ const saveCase = async () => {
       sortOrder: caseForm.sortOrder
       sortOrder: caseForm.sortOrder
     }
     }
     const res = caseForm.id ? await api.updateCase(data) : await api.addCase(currentProblemId.value!, data)
     const res = caseForm.id ? await api.updateCase(data) : await api.addCase(currentProblemId.value!, data)
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       ElMessage.success('保存成功')
       ElMessage.success('保存成功')
       caseDialogVisible.value = false
       caseDialogVisible.value = false
-      loadDetail(currentProblemId.value!)
+      loadDetail(currentProblemId.value!).catch(() => {})
     }
     }
   } catch {
   } catch {
   }
   }
@@ -321,9 +322,9 @@ const deleteCase = (row: ProblemCaseVo) => {
     .then(async () => {
     .then(async () => {
       try {
       try {
         const res = await api.deleteCase({ id: row.id! })
         const res = await api.deleteCase({ id: row.id! })
-        if (res?.code === 200) {
+        if (res?.code === 0 || res?.code === 200) {
           ElMessage.success('删除成功')
           ElMessage.success('删除成功')
-          loadDetail(currentProblemId.value!)
+          loadDetail(currentProblemId.value!).catch(() => {})
         }
         }
       } catch {
       } catch {
       }
       }
@@ -365,10 +366,10 @@ const saveTest = async () => {
       isHidden: testForm.isHidden as ProblemTestDto['isHidden']
       isHidden: testForm.isHidden as ProblemTestDto['isHidden']
     }
     }
     const res = testForm.id ? await api.updateTest(data) : await api.addTest(currentProblemId.value!, data)
     const res = testForm.id ? await api.updateTest(data) : await api.addTest(currentProblemId.value!, data)
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       ElMessage.success('保存成功')
       ElMessage.success('保存成功')
       testDialogVisible.value = false
       testDialogVisible.value = false
-      loadDetail(currentProblemId.value!)
+      loadDetail(currentProblemId.value!).catch(() => {})
     }
     }
   } catch {
   } catch {
   }
   }
@@ -380,9 +381,9 @@ const deleteTest = (row: ProblemTestVo) => {
     .then(async () => {
     .then(async () => {
       try {
       try {
         const res = await api.deleteTest({ id: row.id! })
         const res = await api.deleteTest({ id: row.id! })
-        if (res?.code === 200) {
+        if (res?.code === 0 || res?.code === 200) {
           ElMessage.success('删除成功')
           ElMessage.success('删除成功')
-          loadDetail(currentProblemId.value!)
+          loadDetail(currentProblemId.value!).catch(() => {})
         }
         }
       } catch {
       } catch {
       }
       }
@@ -403,7 +404,7 @@ const toggleTestHidden = async (row: ProblemTestVo, val: number) => {
       isHidden: val as ProblemTestDto['isHidden']
       isHidden: val as ProblemTestDto['isHidden']
     }
     }
     const res = await api.updateTest(data)
     const res = await api.updateTest(data)
-    if (res?.code === 200) {
+    if (res?.code === 0 || res?.code === 200) {
       ElMessage.success('更新成功')
       ElMessage.success('更新成功')
     }
     }
   } catch {
   } catch {
@@ -415,6 +416,18 @@ const handleSelectionChange = (rows: ProblemVo[]) => {
   selectedRows.value = rows
   selectedRows.value = rows
 }
 }
 
 
+const handleDialogClosed = () => {
+  formRef.value?.resetFields()
+}
+
+const handleCaseDialogClosed = () => {
+  caseFormRef.value?.resetFields()
+}
+
+const handleTestDialogClosed = () => {
+  testFormRef.value?.resetFields()
+}
+
 onMounted(() => {
 onMounted(() => {
   loadCategories()
   loadCategories()
   fetchData()
   fetchData()
@@ -488,10 +501,10 @@ onMounted(() => {
         <el-table-column label="创建时间" width="170" align="center">
         <el-table-column label="创建时间" width="170" align="center">
           <template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
           <template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
         </el-table-column>
         </el-table-column>
-        <el-table-column label="操作" width="200" align="center" fixed="right">
+        <el-table-column label="操作" width="250" align="center" fixed="right" class-name="op-column">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button type="primary" link @click="openDetail(row)">
             <el-button type="primary" link @click="openDetail(row)">
-              <el-icon><View /></el-icon>题目评测用例
+              <el-icon><View /></el-icon>样例与用例
             </el-button>
             </el-button>
             <el-button type="primary" link @click="handleEdit(row)">
             <el-button type="primary" link @click="handleEdit(row)">
               <el-icon><Edit /></el-icon>编辑
               <el-icon><Edit /></el-icon>编辑
@@ -517,7 +530,7 @@ onMounted(() => {
     </el-card>
     </el-card>
 
 
     <!-- 新增/编辑题目 -->
     <!-- 新增/编辑题目 -->
-    <el-dialog v-model="dialogVisible" width="960px" draggable>
+    <el-dialog v-model="dialogVisible" width="960px" draggable @closed="handleDialogClosed">
       <template #header>
       <template #header>
         <div class="dialog-header">{{ dialogTitle }}</div>
         <div class="dialog-header">{{ dialogTitle }}</div>
       </template>
       </template>
@@ -575,9 +588,9 @@ onMounted(() => {
     </el-dialog>
     </el-dialog>
 
 
     <!-- 详情弹窗(样例 + 评测用例) -->
     <!-- 详情弹窗(样例 + 评测用例) -->
-    <el-dialog v-model="detailVisible" :title="`题目详情 - ${detail.title || ''}`" width="900px" draggable>
+    <el-dialog v-model="detailVisible" :title="`样例与评测用例 - ${detail.title || ''}`" width="900px" draggable>
       <el-tabs v-model="detailTab">
       <el-tabs v-model="detailTab">
-        <el-tab-pane label="测试样例" name="cases">
+        <el-tab-pane label="题目样例" name="cases">
           <div class="sub-toolbar">
           <div class="sub-toolbar">
             <el-button type="primary" size="small" @click="openCaseAdd">
             <el-button type="primary" size="small" @click="openCaseAdd">
               <el-icon class="el-icon--left"><Plus /></el-icon>新增样例
               <el-icon class="el-icon--left"><Plus /></el-icon>新增样例
@@ -590,7 +603,7 @@ onMounted(() => {
             <el-table-column prop="input" label="输入" min-width="180" show-overflow-tooltip />
             <el-table-column prop="input" label="输入" min-width="180" show-overflow-tooltip />
             <el-table-column prop="output" label="输出" min-width="180" show-overflow-tooltip />
             <el-table-column prop="output" label="输出" min-width="180" show-overflow-tooltip />
             <el-table-column prop="description" label="说明" min-width="140" show-overflow-tooltip />
             <el-table-column prop="description" label="说明" min-width="140" show-overflow-tooltip />
-            <el-table-column label="操作" width="120" align="center">
+            <el-table-column label="操作" width="120" align="center" class-name="op-column">
               <template #default="{ row }">
               <template #default="{ row }">
                 <el-button type="primary" link size="small" @click="openCaseEdit(row)">编辑</el-button>
                 <el-button type="primary" link size="small" @click="openCaseEdit(row)">编辑</el-button>
                 <el-button type="danger" link size="small" @click="deleteCase(row)">删除</el-button>
                 <el-button type="danger" link size="small" @click="deleteCase(row)">删除</el-button>
@@ -620,7 +633,7 @@ onMounted(() => {
                 />
                 />
               </template>
               </template>
             </el-table-column>
             </el-table-column>
-            <el-table-column label="操作" width="120" align="center">
+            <el-table-column label="操作" width="120" align="center" class-name="op-column">
               <template #default="{ row }">
               <template #default="{ row }">
                 <el-button type="primary" link size="small" @click="openTestEdit(row)">编辑</el-button>
                 <el-button type="primary" link size="small" @click="openTestEdit(row)">编辑</el-button>
                 <el-button type="danger" link size="small" @click="deleteTest(row)">删除</el-button>
                 <el-button type="danger" link size="small" @click="deleteTest(row)">删除</el-button>
@@ -632,8 +645,8 @@ onMounted(() => {
     </el-dialog>
     </el-dialog>
 
 
     <!-- 样例编辑 -->
     <!-- 样例编辑 -->
-    <el-dialog v-model="caseDialogVisible" :title="caseForm.id ? '编辑样例' : '新增样例'" width="700px" draggable>
-      <el-form ref="caseFormRef" :model="caseForm" :rules="caseRules" label-width="80px">
+    <el-dialog v-model="caseDialogVisible" :title="caseForm.id ? '编辑样例' : '新增样例'" width="700px" draggable @closed="handleCaseDialogClosed">
+      <el-form ref="caseFormRef" :model="caseForm" :rules="caseRules" label-position="top">
         <el-form-item label="序号">
         <el-form-item label="序号">
           <el-input-number v-model="caseForm.sortOrder" :min="1" />
           <el-input-number v-model="caseForm.sortOrder" :min="1" />
         </el-form-item>
         </el-form-item>
@@ -656,8 +669,8 @@ onMounted(() => {
     </el-dialog>
     </el-dialog>
 
 
     <!-- 评测用例编辑 -->
     <!-- 评测用例编辑 -->
-    <el-dialog v-model="testDialogVisible" :title="testForm.id ? '编辑评测用例' : '新增评测用例'" width="560px" draggable>
-      <el-form ref="testFormRef" :model="testForm" :rules="testRules" label-width="80px">
+    <el-dialog v-model="testDialogVisible" :title="testForm.id ? '编辑评测用例' : '新增评测用例'" width="560px" draggable @closed="handleTestDialogClosed">
+      <el-form ref="testFormRef" :model="testForm" :rules="testRules" label-position="top">
         <el-form-item label="序号">
         <el-form-item label="序号">
           <el-input-number v-model="testForm.sortOrder" :min="1" />
           <el-input-number v-model="testForm.sortOrder" :min="1" />
         </el-form-item>
         </el-form-item>
@@ -697,6 +710,10 @@ onMounted(() => {
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }
 
 
+:deep(.el-table .op-column .cell) {
+  white-space: nowrap;
+}
+
 .pagination {
 .pagination {
   display: flex;
   display: flex;
   justify-content: flex-end;
   justify-content: flex-end;

+ 12 - 9
src/view/admin/UserManageView.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { getUserController } from '@/api/user-controller'
 import { getUserController } from '@/api/user-controller'
 import type { QueryByPageParams, UpdateUserStatusDto, UserDto, UserVo } from '@/api/models'
 import type { QueryByPageParams, UpdateUserStatusDto, UserDto, UserVo } from '@/api/models'
-import { ElMessage, ElMessageBox, type FormInstance, type FormRules, ElIcon } from 'element-plus'
+import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
 import { Plus, RefreshRight, Search, Delete, Edit } from '@element-plus/icons-vue'
 import { Plus, RefreshRight, Search, Delete, Edit } from '@element-plus/icons-vue'
 import { ref, reactive, computed } from 'vue'
 import { ref, reactive, computed } from 'vue'
 
 
@@ -13,7 +13,7 @@ const searchForm = reactive({
   username: '',
   username: '',
   account: '',
   account: '',
   role: '',
   role: '',
-  enable: 1
+  enable: undefined as number | undefined
 })
 })
 
 
 // 用户表单(新增/编辑)
 // 用户表单(新增/编辑)
@@ -41,8 +41,7 @@ const formRules: FormRules = {
     { min: 4, max: 20, message: '密码长度需在 4 到 20 个字符之间', trigger: 'blur' }
     { min: 4, max: 20, message: '密码长度需在 4 到 20 个字符之间', trigger: 'blur' }
   ],
   ],
   role: [
   role: [
-    { required: true, message: '请选择角色', trigger: 'change' },
-    { min: 4, max: 32, message: '角色长度需在 4 到 32 个字符之间', trigger: 'change' }
+    { required: true, message: '请选择角色', trigger: 'change' }
   ]
   ]
 }
 }
 
 
@@ -194,7 +193,7 @@ const handleDialogClosed = () => {
 
 
 // 删除
 // 删除
 const handleDelete = (row: UserVo) => {
 const handleDelete = (row: UserVo) => {
-  ElMessageBox.confirm('确认删除该用户吗?', '提示', {
+  ElMessageBox.confirm('确认删除该用户吗?', '提示', {
     type: 'warning'
     type: 'warning'
   }).then(async () => {
   }).then(async () => {
     try {
     try {
@@ -215,7 +214,7 @@ const handleBatchDelete = () => {
     ElMessage.warning('请选择要删除的数据')
     ElMessage.warning('请选择要删除的数据')
     return
     return
   }
   }
-  ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条数据吗?`, '提示', {
+  ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条数据吗?`, '提示', {
     type: 'warning'
     type: 'warning'
   }).then(async () => {
   }).then(async () => {
     try {
     try {
@@ -335,7 +334,7 @@ fetchData()
             <el-avatar v-else :size="40">{{ row.username?.charAt(0) || '?' }}</el-avatar>
             <el-avatar v-else :size="40">{{ row.username?.charAt(0) || '?' }}</el-avatar>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column label="操作" width="180" align="center" fixed="right">
+        <el-table-column label="操作" width="180" align="center" fixed="right" class-name="op-column">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button type="primary" link @click="handleEdit(row)">
             <el-button type="primary" link @click="handleEdit(row)">
               <el-icon><Edit /></el-icon>编辑
               <el-icon><Edit /></el-icon>编辑
@@ -363,7 +362,7 @@ fetchData()
 
 
     <!-- 新增/编辑弹窗 -->
     <!-- 新增/编辑弹窗 -->
     <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" draggable @closed="handleDialogClosed">
     <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" draggable @closed="handleDialogClosed">
-      <el-form ref="formRef" :model="userForm" :rules="formRules" label-width="80px">
+      <el-form ref="formRef" :model="userForm" :rules="formRules" label-position="top">
         <el-form-item label="用户名" prop="username">
         <el-form-item label="用户名" prop="username">
           <el-input v-model="userForm.username" placeholder="请输入用户名" />
           <el-input v-model="userForm.username" placeholder="请输入用户名" />
         </el-form-item>
         </el-form-item>
@@ -405,6 +404,10 @@ fetchData()
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }
 
 
+:deep(.el-table .op-column .cell) {
+  white-space: nowrap;
+}
+
 .pagination {
 .pagination {
   display: flex;
   display: flex;
   justify-content: flex-end;
   justify-content: flex-end;
@@ -413,7 +416,7 @@ fetchData()
 
 
 .tip {
 .tip {
   font-size: 12px;
   font-size: 12px;
-  color: #909399;
+  color: var(--el-text-color-secondary);
   line-height: 1.4;
   line-height: 1.4;
 }
 }
 </style>
 </style>

+ 1 - 1
src/view/contest/ContestDetailView.vue

@@ -283,7 +283,7 @@ onMounted(fetchDetail)
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   gap: 12px;
   gap: 12px;
-  color: #606266;
+  color: var(--el-text-color-regular);
 }
 }
 
 
 .locked-icon {
 .locked-icon {

+ 1 - 1
src/view/contest/ContestRankingView.vue

@@ -107,7 +107,7 @@ onMounted(() => {
 
 
 .trophy-icon {
 .trophy-icon {
   font-size: 22px;
   font-size: 22px;
-  color: #e6a23c;
+  color: var(--el-color-warning);
 }
 }
 
 
 .title {
 .title {

+ 3 - 3
src/view/problem/ProblemDetailView.vue

@@ -308,7 +308,7 @@ onMounted(() => {
 }
 }
 
 
 .case-desc {
 .case-desc {
-  color: #606266;
+  color: var(--el-text-color-regular);
   font-size: 13px;
   font-size: 13px;
   margin-bottom: 6px;
   margin-bottom: 6px;
 }
 }
@@ -324,14 +324,14 @@ onMounted(() => {
 
 
 .case-label {
 .case-label {
   font-size: 13px;
   font-size: 13px;
-  color: #909399;
+  color: var(--el-text-color-secondary);
   margin-bottom: 4px;
   margin-bottom: 4px;
 }
 }
 
 
 .case-pre {
 .case-pre {
   margin: 0;
   margin: 0;
   padding: 12px;
   padding: 12px;
-  background: #f5f7fa;
+  background: var(--el-fill-color-light);
   border-radius: 6px;
   border-radius: 6px;
   white-space: pre-wrap;
   white-space: pre-wrap;
   word-break: break-word;
   word-break: break-word;

+ 1 - 1
src/view/submission/SubmissionListView.vue

@@ -148,7 +148,7 @@ onMounted(() => {
 .judge-detail {
 .judge-detail {
   margin: 0;
   margin: 0;
   padding: 12px;
   padding: 12px;
-  background: #f5f7fa;
+  background: var(--el-fill-color-light);
   border-radius: 6px;
   border-radius: 6px;
   white-space: pre-wrap;
   white-space: pre-wrap;
   word-break: break-word;
   word-break: break-word;