Initial commit
1
admin/client/public/china.json
Normal file
@@ -43,6 +43,45 @@ service.interceptors.response.use(
|
||||
*/
|
||||
response => {
|
||||
const res = response.data
|
||||
|
||||
// Replace localhost/127.0.0.1 in response data with current window location hostname if needed
|
||||
// This helps when accessing admin panel from LAN but backend returns localhost URLs
|
||||
const replaceUrl = (data) => {
|
||||
if (!data) return data
|
||||
if (typeof data === 'string') {
|
||||
// Replace http://127.0.0.1:8000 or http://localhost:8000
|
||||
// with http://<current-host>:8000
|
||||
// We assume backend is on port 8000.
|
||||
// If we are proxying, we might want to replace with relative path or proxy target.
|
||||
// But simpler is to just replace the IP part with current window hostname if we are in dev/lan.
|
||||
// However, hardcoding 8000 might be risky if backend port changes.
|
||||
// Let's stick to replacing specific localhost/127.0.0.1:8000 patterns.
|
||||
const currentHost = window.location.hostname;
|
||||
// Only replace if current host is NOT localhost/127.0.0.1 (meaning we are on LAN)
|
||||
if (currentHost !== 'localhost' && currentHost !== '127.0.0.1') {
|
||||
return data.replace(/https?:\/\/(localhost|127\.0\.0\.1):8000/g, `http://${currentHost}:8000`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(replaceUrl)
|
||||
}
|
||||
if (typeof data === 'object') {
|
||||
Object.keys(data).forEach(key => {
|
||||
data[key] = replaceUrl(data[key])
|
||||
})
|
||||
return data
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Apply replacement
|
||||
try {
|
||||
replaceUrl(res);
|
||||
} catch (e) {
|
||||
console.error('Failed to replace URLs', e);
|
||||
}
|
||||
|
||||
if(res.code>=200 && res.code<400){
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -70,16 +70,15 @@ export default {
|
||||
if (echarts.getMap('china')) {
|
||||
this.setOptions(this.chartData)
|
||||
} else {
|
||||
// Fetch China Map GeoJSON
|
||||
// Using a reliable public source
|
||||
axios.get('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
|
||||
// Fetch China Map GeoJSON from local static assets
|
||||
// This ensures the map loads even without internet access or on non-local environments
|
||||
axios.get(process.env.BASE_URL + 'china.json')
|
||||
.then(response => {
|
||||
echarts.registerMap('china', response.data)
|
||||
this.setOptions(this.chartData)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to load China map data:', error)
|
||||
// Fallback or show error? For now just log.
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
.theme-dark {
|
||||
.card-panel {
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
background-color: rgba(255, 250, 240, 0.1); /* 半透明米白色 */
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
@@ -212,20 +212,22 @@ export default {
|
||||
&:hover {
|
||||
transform: translateY(-5px) scale(1.02);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
|
||||
background-color: rgba(255, 250, 240, 0.15);
|
||||
|
||||
.card-panel-icon-wrapper {
|
||||
transform: scale(1.1);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.card-panel-icon-wrapper {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.card-panel-icon {
|
||||
fill: #fff !important;
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
fill: currentColor !important;
|
||||
}
|
||||
|
||||
.card-panel-text {
|
||||
@@ -236,14 +238,35 @@ export default {
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Gradients - 使用 background-image 确保不被覆盖 */
|
||||
&.card-panel-blue { background-image: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }
|
||||
&.card-panel-purple { background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
||||
&.card-panel-orange { background-image: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }
|
||||
&.card-panel-green { background-image: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); }
|
||||
&.card-panel-red { background-image: linear-gradient(135deg, #fa709a 0%, #fee140 100%); }
|
||||
&.card-panel-yellow { background-image: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%); }
|
||||
&.card-panel-pink { background-image: linear-gradient(135deg, #ff0844 0%, #ffb199 100%); }
|
||||
/* Colors for icons in Dark Mode (since background is now uniform) */
|
||||
&.card-panel-blue {
|
||||
.card-panel-icon { color: #4facfe; }
|
||||
.card-panel-icon-wrapper { color: #4facfe; }
|
||||
}
|
||||
&.card-panel-purple {
|
||||
.card-panel-icon { color: #8fd3f4; } /* Lighter purple for dark mode visibility */
|
||||
.card-panel-icon-wrapper { color: #8fd3f4; }
|
||||
}
|
||||
&.card-panel-orange {
|
||||
.card-panel-icon { color: #ff9a9e; }
|
||||
.card-panel-icon-wrapper { color: #ff9a9e; }
|
||||
}
|
||||
&.card-panel-green {
|
||||
.card-panel-icon { color: #43e97b; }
|
||||
.card-panel-icon-wrapper { color: #43e97b; }
|
||||
}
|
||||
&.card-panel-red {
|
||||
.card-panel-icon { color: #fa709a; }
|
||||
.card-panel-icon-wrapper { color: #fa709a; }
|
||||
}
|
||||
&.card-panel-yellow {
|
||||
.card-panel-icon { color: #fbc2eb; }
|
||||
.card-panel-icon-wrapper { color: #fbc2eb; }
|
||||
}
|
||||
&.card-panel-pink {
|
||||
.card-panel-icon { color: #ff0844; }
|
||||
.card-panel-icon-wrapper { color: #ff0844; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ module.exports = {
|
||||
proxy: {
|
||||
'/api': {
|
||||
// target: 'http://localhost:8000',
|
||||
target: process.env.PROXY_TARGET || 'http://127.0.0.1:8000',
|
||||
target: process.env.PROXY_TARGET || 'http://192.168.5.95:8000',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 574 KiB |
|
Before Width: | Height: | Size: 49 KiB |
11
admin/server/.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.git/
|
||||
.vscode/
|
||||
.vs/
|
||||
dist/
|
||||
celerybeat.pid
|
||||
celerybeat-schedule.*
|
||||
db.sqlite3
|
||||
.env
|
||||
@@ -9,7 +9,7 @@ class Command(BaseCommand):
|
||||
help = 'Populate database with business mock data using existing local images'
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.base_url = "http://127.0.0.1:8000"
|
||||
self.base_url = "http://192.168.5.95:8000"
|
||||
self.media_root = settings.MEDIA_ROOT
|
||||
self.projects_dir = os.path.join(self.media_root, 'projects')
|
||||
self.showcases_dir = os.path.join(self.media_root, 'showcases')
|
||||
|
||||
@@ -210,6 +210,7 @@ class DashboardStatsView(APIView):
|
||||
# Use default color if type not found
|
||||
color = type_colors.get(type_code, '#909399')
|
||||
pie_chart_data.append({
|
||||
'type': type_code,
|
||||
'name': type_mapping.get(type_code, type_code),
|
||||
'value': item['total_students'],
|
||||
'itemStyle': { 'color': color }
|
||||
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -10,3 +10,4 @@ djangorestframework-simplejwt==4.8.0
|
||||
drf-yasg==1.20.0
|
||||
psutil==5.9.0
|
||||
redis==4.5.5
|
||||
gunicorn
|
||||
|
||||
@@ -1,6 +1,54 @@
|
||||
const app = getApp()
|
||||
|
||||
function getOrigin(url) {
|
||||
if (!url) return ''
|
||||
const match = url.match(/^(https?:\/\/[^/]+)/)
|
||||
return match ? match[1] : ''
|
||||
}
|
||||
|
||||
function replaceUrl(data, targetOrigin) {
|
||||
if (!data || !targetOrigin) return data
|
||||
|
||||
if (typeof data === 'string') {
|
||||
// Replace localhost/127.0.0.1 with target origin
|
||||
// Handle both http and https, though localhost usually http
|
||||
return data.replace(/https?:\/\/(localhost|127\.0\.0\.1):8000/g, targetOrigin)
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(item => replaceUrl(item, targetOrigin))
|
||||
}
|
||||
|
||||
if (typeof data === 'object') {
|
||||
const newData = {}
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
newData[key] = replaceUrl(data[key], targetOrigin)
|
||||
}
|
||||
}
|
||||
return newData
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
function unwrap(data) {
|
||||
// First, apply URL replacement if needed
|
||||
// We need to access app.globalData which might not be ready if called too early,
|
||||
// but usually request is called after app launch.
|
||||
// Safely get app instance again inside function
|
||||
try {
|
||||
const app = getApp()
|
||||
if (app && app.globalData && app.globalData.baseUrl) {
|
||||
const origin = getOrigin(app.globalData.baseUrl)
|
||||
if (origin) {
|
||||
data = replaceUrl(data, origin)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('URL replacement failed', e)
|
||||
}
|
||||
|
||||
if (data && typeof data === 'object' && 'code' in data && 'data' in data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||