Spaces:
Running
Running
Upload 27 files
Browse files- index.css +14 -0
- index.tsx +2 -0
- pages/UserList.tsx +3 -2
- server.js +40 -24
index.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
@tailwind base;
|
| 3 |
+
@tailwind components;
|
| 4 |
+
@tailwind utilities;
|
| 5 |
+
|
| 6 |
+
body {
|
| 7 |
+
margin: 0;
|
| 8 |
+
font-family: 'Noto Sans SC', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
| 9 |
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
| 10 |
+
sans-serif;
|
| 11 |
+
-webkit-font-smoothing: antialiased;
|
| 12 |
+
-moz-osx-font-smoothing: grayscale;
|
| 13 |
+
background-color: #f9fafb;
|
| 14 |
+
}
|
index.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
|
|
| 1 |
import React from 'react';
|
| 2 |
import ReactDOM from 'react-dom/client';
|
| 3 |
import App from './App';
|
|
|
|
| 4 |
|
| 5 |
const rootElement = document.getElementById('root');
|
| 6 |
if (!rootElement) {
|
|
|
|
| 1 |
+
|
| 2 |
import React from 'react';
|
| 3 |
import ReactDOM from 'react-dom/client';
|
| 4 |
import App from './App';
|
| 5 |
+
import './index.css';
|
| 6 |
|
| 7 |
const rootElement = document.getElementById('root');
|
| 8 |
if (!rootElement) {
|
pages/UserList.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import React, { useState, useEffect } from 'react';
|
| 2 |
import { User, UserRole, UserStatus, School } from '../types';
|
| 3 |
import { api } from '../services/api';
|
|
@@ -80,7 +81,7 @@ export const UserList: React.FC = () => {
|
|
| 80 |
</thead>
|
| 81 |
<tbody className="divide-y divide-gray-100">
|
| 82 |
{users.map(user => {
|
| 83 |
-
const isSelf =
|
| 84 |
return (
|
| 85 |
<tr key={user._id || user.id} className="hover:bg-gray-50">
|
| 86 |
<td className="px-4 py-3">
|
|
@@ -163,4 +164,4 @@ export const UserList: React.FC = () => {
|
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
);
|
| 166 |
-
};
|
|
|
|
| 1 |
+
|
| 2 |
import React, { useState, useEffect } from 'react';
|
| 3 |
import { User, UserRole, UserStatus, School } from '../types';
|
| 4 |
import { api } from '../services/api';
|
|
|
|
| 81 |
</thead>
|
| 82 |
<tbody className="divide-y divide-gray-100">
|
| 83 |
{users.map(user => {
|
| 84 |
+
const isSelf = currentUser && (user._id === currentUser._id || user.username === currentUser.username);
|
| 85 |
return (
|
| 86 |
<tr key={user._id || user.id} className="hover:bg-gray-50">
|
| 87 |
<td className="px-4 py-3">
|
|
|
|
| 164 |
</div>
|
| 165 |
</div>
|
| 166 |
);
|
| 167 |
+
};
|
server.js
CHANGED
|
@@ -157,21 +157,29 @@ const ConfigModel = mongoose.model('Config', ConfigSchema);
|
|
| 157 |
const initData = async () => {
|
| 158 |
if (InMemoryDB.isFallback) return;
|
| 159 |
try {
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
defaultSchool = await School.create({ name: '第一实验小学', code: 'EXP01' });
|
| 165 |
-
console.log('Initialized Default School');
|
| 166 |
-
} else {
|
| 167 |
-
defaultSchool = await School.findOne();
|
| 168 |
}
|
| 169 |
|
| 170 |
-
// 2. Create Admin
|
| 171 |
-
const
|
| 172 |
-
if (
|
| 173 |
-
await User.create(
|
| 174 |
-
{
|
| 175 |
username: 'admin',
|
| 176 |
password: 'admin',
|
| 177 |
role: 'ADMIN',
|
|
@@ -179,22 +187,30 @@ const initData = async () => {
|
|
| 179 |
schoolId: defaultSchool._id.toString(),
|
| 180 |
trueName: '超级管理员',
|
| 181 |
email: 'admin@system.com'
|
| 182 |
-
|
| 183 |
-
|
| 184 |
}
|
| 185 |
|
| 186 |
-
// 3. Create Default Subjects
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
}
|
|
|
|
|
|
|
| 196 |
} catch (err) {
|
| 197 |
-
console.error('Init Data Error', err);
|
| 198 |
}
|
| 199 |
};
|
| 200 |
mongoose.connection.once('open', initData);
|
|
|
|
| 157 |
const initData = async () => {
|
| 158 |
if (InMemoryDB.isFallback) return;
|
| 159 |
try {
|
| 160 |
+
console.log('🔄 Checking database initialization...');
|
| 161 |
+
|
| 162 |
+
// --- FIX: Drop Legacy Indexes ---
|
| 163 |
+
// In multi-tenancy, 'name' on subjects should NOT be unique globally.
|
| 164 |
+
// We try to drop the index 'name_1' if it exists.
|
| 165 |
+
try {
|
| 166 |
+
await mongoose.connection.collection('subjects').dropIndex('name_1');
|
| 167 |
+
console.log('⚠️ Dropped legacy unique index on subjects collection to support multi-school subjects.');
|
| 168 |
+
} catch (e) {
|
| 169 |
+
// Index might not exist or error code 27 (index not found), ignore
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
// 1. Create Default School (Upsert)
|
| 173 |
+
let defaultSchool = await School.findOne({ code: 'EXP01' });
|
| 174 |
+
if (!defaultSchool) {
|
| 175 |
defaultSchool = await School.create({ name: '第一实验小学', code: 'EXP01' });
|
| 176 |
+
console.log('✅ Initialized Default School');
|
|
|
|
|
|
|
| 177 |
}
|
| 178 |
|
| 179 |
+
// 2. Create Admin (Upsert)
|
| 180 |
+
const adminExists = await User.findOne({ username: 'admin' });
|
| 181 |
+
if (!adminExists) {
|
| 182 |
+
await User.create({
|
|
|
|
| 183 |
username: 'admin',
|
| 184 |
password: 'admin',
|
| 185 |
role: 'ADMIN',
|
|
|
|
| 187 |
schoolId: defaultSchool._id.toString(),
|
| 188 |
trueName: '超级管理员',
|
| 189 |
email: 'admin@system.com'
|
| 190 |
+
});
|
| 191 |
+
console.log('✅ Initialized Admin User');
|
| 192 |
}
|
| 193 |
|
| 194 |
+
// 3. Create Default Subjects (Upsert)
|
| 195 |
+
// Use updateOne with upsert to prevent E11000 errors if run multiple times
|
| 196 |
+
const defaultSubjects = [
|
| 197 |
+
{ schoolId: defaultSchool._id.toString(), name: '语文', code: 'CHI', color: '#ef4444', excellenceThreshold: 90 },
|
| 198 |
+
{ schoolId: defaultSchool._id.toString(), name: '数学', code: 'MAT', color: '#3b82f6', excellenceThreshold: 90 },
|
| 199 |
+
{ schoolId: defaultSchool._id.toString(), name: '英语', code: 'ENG', color: '#f59e0b', excellenceThreshold: 90 },
|
| 200 |
+
{ schoolId: defaultSchool._id.toString(), name: '科学', code: 'SCI', color: '#10b981', excellenceThreshold: 85 }
|
| 201 |
+
];
|
| 202 |
+
|
| 203 |
+
for (const sub of defaultSubjects) {
|
| 204 |
+
await SubjectModel.updateOne(
|
| 205 |
+
{ schoolId: sub.schoolId, name: sub.name }, // Check if subject exists for this school
|
| 206 |
+
{ $set: sub },
|
| 207 |
+
{ upsert: true }
|
| 208 |
+
);
|
| 209 |
}
|
| 210 |
+
console.log('✅ Initialized/Updated Default Subjects');
|
| 211 |
+
|
| 212 |
} catch (err) {
|
| 213 |
+
console.error('❌ Init Data Error', err);
|
| 214 |
}
|
| 215 |
};
|
| 216 |
mongoose.connection.once('open', initData);
|