Initial commit

This commit is contained in:
admin
2025-12-10 17:22:27 +08:00
parent 9292ef8d8d
commit 61c61a77a2
17 changed files with 142 additions and 19 deletions

File diff suppressed because one or more lines are too long

View File

@@ -43,6 +43,45 @@ service.interceptors.response.use(
*/ */
response => { response => {
const res = response.data 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){ if(res.code>=200 && res.code<400){
return res return res
} }

View File

@@ -70,16 +70,15 @@ export default {
if (echarts.getMap('china')) { if (echarts.getMap('china')) {
this.setOptions(this.chartData) this.setOptions(this.chartData)
} else { } else {
// Fetch China Map GeoJSON // Fetch China Map GeoJSON from local static assets
// Using a reliable public source // This ensures the map loads even without internet access or on non-local environments
axios.get('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json') axios.get(process.env.BASE_URL + 'china.json')
.then(response => { .then(response => {
echarts.registerMap('china', response.data) echarts.registerMap('china', response.data)
this.setOptions(this.chartData) this.setOptions(this.chartData)
}) })
.catch(error => { .catch(error => {
console.error('Failed to load China map data:', error) console.error('Failed to load China map data:', error)
// Fallback or show error? For now just log.
}) })
} }
}, },

View File

@@ -204,7 +204,7 @@ export default {
.theme-dark { .theme-dark {
.card-panel { .card-panel {
color: #fff; color: #fff;
background-color: rgba(255, 255, 255, 0.05); background-color: rgba(255, 250, 240, 0.1); /* 半透明米白色 */
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
@@ -212,20 +212,22 @@ export default {
&:hover { &:hover {
transform: translateY(-5px) scale(1.02); transform: translateY(-5px) scale(1.02);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
background-color: rgba(255, 250, 240, 0.15);
.card-panel-icon-wrapper { .card-panel-icon-wrapper {
transform: scale(1.1); transform: scale(1.1);
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.2);
} }
} }
.card-panel-icon-wrapper { .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 { .card-panel-icon {
fill: #fff !important; font-size: 32px;
color: #fff; fill: currentColor !important;
} }
.card-panel-text { .card-panel-text {
@@ -236,14 +238,35 @@ export default {
text-shadow: 0 2px 4px rgba(0,0,0,0.2); text-shadow: 0 2px 4px rgba(0,0,0,0.2);
} }
/* Gradients - 使用 background-image 确保不被覆盖 */ /* Colors for icons in Dark Mode (since background is now uniform) */
&.card-panel-blue { background-image: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); } &.card-panel-blue {
&.card-panel-purple { background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .card-panel-icon { color: #4facfe; }
&.card-panel-orange { background-image: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); } .card-panel-icon-wrapper { color: #4facfe; }
&.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-purple {
&.card-panel-yellow { background-image: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%); } .card-panel-icon { color: #8fd3f4; } /* Lighter purple for dark mode visibility */
&.card-panel-pink { background-image: linear-gradient(135deg, #ff0844 0%, #ffb199 100%); } .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; }
}
} }
} }

View File

@@ -41,7 +41,7 @@ module.exports = {
proxy: { proxy: {
'/api': { '/api': {
// target: 'http://localhost:8000', // 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 changeOrigin: true
} }
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,11 @@
venv/
__pycache__/
*.pyc
.git/
.vscode/
.vs/
dist/
celerybeat.pid
celerybeat-schedule.*
db.sqlite3
.env

View File

@@ -9,7 +9,7 @@ class Command(BaseCommand):
help = 'Populate database with business mock data using existing local images' help = 'Populate database with business mock data using existing local images'
def handle(self, *args, **kwargs): 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.media_root = settings.MEDIA_ROOT
self.projects_dir = os.path.join(self.media_root, 'projects') self.projects_dir = os.path.join(self.media_root, 'projects')
self.showcases_dir = os.path.join(self.media_root, 'showcases') self.showcases_dir = os.path.join(self.media_root, 'showcases')

View File

@@ -210,6 +210,7 @@ class DashboardStatsView(APIView):
# Use default color if type not found # Use default color if type not found
color = type_colors.get(type_code, '#909399') color = type_colors.get(type_code, '#909399')
pie_chart_data.append({ pie_chart_data.append({
'type': type_code,
'name': type_mapping.get(type_code, type_code), 'name': type_mapping.get(type_code, type_code),
'value': item['total_students'], 'value': item['total_students'],
'itemStyle': { 'color': color } 'itemStyle': { 'color': color }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -10,3 +10,4 @@ djangorestframework-simplejwt==4.8.0
drf-yasg==1.20.0 drf-yasg==1.20.0
psutil==5.9.0 psutil==5.9.0
redis==4.5.5 redis==4.5.5
gunicorn

View File

@@ -1,6 +1,54 @@
const app = getApp() 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) { 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) { if (data && typeof data === 'object' && 'code' in data && 'data' in data) {
return data.data return data.data
} }