Update 2026-06-10 11:45:26

This commit is contained in:
yi
2026-06-10 11:45:27 +08:00
parent 5f25392908
commit 29d1359859
14 changed files with 3161 additions and 300 deletions
+92
View File
@@ -0,0 +1,92 @@
# --- Python ---
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg
*.egg-info/
.eggs/
dist/
build/
pip-log.txt
pip-delete-this-directory.txt
# Virtual environments
.venv/
venv/
ENV/
env/
.python-version
# --- Django ---
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Collected static (if STATIC_ROOT differs from source)
staticfiles/
collected_static/
# Compiled translations (generate via compilemessages)
*.mo
# --- Environment & secrets ---
.env
.env.*
!.env.example
*.pem
*.key
# --- IDE / Editor ---
.idea/
.vscode/
*.sublime-project
*.sublime-workspace
*.swp
*.swo
*~
# --- OS ---
.DS_Store
.DS_Store?
Thumbs.db
ehthumbs.db
desktop.ini
# --- Cursor / local workflow ---
.cursor/
.work/
# --- Test & coverage ---
.coverage
.coverage.*
htmlcov/
.tox/
.nox/
.pytest_cache/
.mypy_cache/
.dmypy.json
dmypy.json
# --- Docker (local overrides & volumes) ---
docker-compose.override.yml
docker-compose.local.yml
*.volume/
# --- User uploads (MEDIA_ROOT) ---
# Keep template docs under upload/doc/
upload/data/*
!upload/data/.gitkeep
upload/inventory/*
!upload/inventory/.gitkeep
upload/project/*
!upload/project/.gitkeep
upload/offer sheet/*
!upload/offer sheet/.gitkeep
# --- Misc ---
*.bak
*.tmp
*.orig
+3 -3
View File
@@ -2,7 +2,7 @@
## 当前进度
- **当前步骤**:步骤 10
- **当前步骤**:步骤 11
- **状态**success
- **开始时间**2026-6-10 09:51:31
- **完成时间**
@@ -71,9 +71,9 @@
- **说明**:健康验证通过。Web 服务(6010)已成功启动并可以正常访问登录页面。
### 步骤 11:提交
- **状态**⏭️ 未开始
- **状态**success
- **完成时间**
- **说明**
- **说明**所有步骤已完成。项目已成功部署并提交。
## 错误记录
+45
View File
@@ -0,0 +1,45 @@
-- 销售订单示例数据(依赖 basedata_partner / basedata_material 等基础数据)
-- 导入: mysql -uroot -proot mis < Install/SQL/sale_order_sample.sql
SET NAMES utf8mb4;
INSERT INTO `sale_saleorder`
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
SELECT 1,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00001','2025-05-10','2025-05-25','办公设备采购订单','含笔记本电脑及服务器,需上门安装调试。','张经理','13800138001',NULL,'上海市浦东新区张江高科技园区科苑路88号','10',24299.00,0.00,'9',1,17,11
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00001');
INSERT INTO `sale_saleorder`
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
SELECT 2,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00002','2025-05-15','2025-06-01','配电设备批量采购','项目现场配电改造用材。','李工','13900139002',NULL,'江苏省南京市江宁区将军大道29号','10',6400.00,200.00,'1',1,17,11
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00002');
INSERT INTO `sale_saleorder`
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
SELECT 3,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00003','2025-05-18','2025-05-22','水果批发季节订单','当季鲜果直采,分批配送。','王采购','13700137003',NULL,'浙江省杭州市余杭区良渚街道物流园A区','10',13962.00,0.00,'0',1,17,11
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00003');
INSERT INTO `sale_saleorder`
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
SELECT 4,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00004','2025-05-20','2025-05-28','劳保用品集中采购','车间一线员工劳保手套补充采购。','赵主任','13600136004',NULL,'安徽省合肥市蜀山区望江西路666号','10',5500.00,0.00,'9',1,17,11
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00004');
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 1,2.0000,NULL,7150.0000,'0.00',NOW(),0,NULL,1,53,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=1);
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 2,1.0000,NULL,9999.0000,'0.00',NOW(),0,NULL,1,54,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=2);
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 3,20.0000,NULL,320.0000,'0.00',NOW(),0,NULL,2,52,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=3);
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 4,500.0000,NULL,10.9200,'0.00',NOW(),0,NULL,3,40,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=4);
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 5,300.0000,NULL,15.9900,'0.00',NOW(),0,NULL,3,41,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=5);
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 6,200.0000,NULL,18.5250,'0.00',NOW(),0,NULL,3,42,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=6);
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
SELECT 7,1000.0000,NULL,5.5000,'0.00',NOW(),0,NULL,4,5,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=7);
+170
View File
@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
"""导入销售订单示例数据。用法: python Install/load_sale_sample.py"""
import os
import sys
import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mis.settings')
import django
django.setup()
from decimal import Decimal
from django.contrib.auth.models import User
from django.db import transaction
from basedata.models import Partner, Material, Organization
from sale.models import SaleOrder, SaleItem
def recalc_order_amount(order):
total = Decimal('0')
for item in SaleItem.objects.filter(master=order):
price = item.sale_price or Decimal('0')
total += price * item.cnt
SaleOrder.objects.filter(pk=order.pk).update(amount=total)
order.amount = total
return total
SAMPLE_ORDERS = [
{
'code': 'SO00001',
'title': u'办公设备采购订单',
'order_date': datetime.date(2025, 5, 10),
'deliver_date': datetime.date(2025, 5, 25),
'contact': u'张经理',
'phone': '13800138001',
'deliver_address': u'上海市浦东新区张江高科技园区科苑路88号',
'description': u'含笔记本电脑及服务器,需上门安装调试。',
'status': '9',
'discount_amount': 0,
'items': [
{'material_code': 'IT5001', 'cnt': 2, 'sale_price': 7150.00},
{'material_code': 'IT5002', 'cnt': 1, 'sale_price': 9999.00},
],
},
{
'code': 'SO00002',
'title': u'配电设备批量采购',
'order_date': datetime.date(2025, 5, 15),
'deliver_date': datetime.date(2025, 6, 1),
'contact': u'李工',
'phone': '13900139002',
'deliver_address': u'江苏省南京市江宁区将军大道29号',
'description': u'项目现场配电改造用材。',
'status': '1',
'discount_amount': 200.00,
'items': [
{'material_code': 'IT9981', 'cnt': 20, 'sale_price': 320.00},
],
},
{
'code': 'SO00003',
'title': u'水果批发季节订单',
'order_date': datetime.date(2025, 5, 18),
'deliver_date': datetime.date(2025, 5, 22),
'contact': u'王采购',
'phone': '13700137003',
'deliver_address': u'浙江省杭州市余杭区良渚街道物流园A区',
'description': u'当季鲜果直采,分批配送。',
'status': '0',
'discount_amount': 0,
'items': [
{'material_code': 'IT9001', 'cnt': 500, 'sale_price': 11.00},
{'material_code': 'IT9002', 'cnt': 300, 'sale_price': 16.00},
{'material_code': 'IT9003', 'cnt': 200, 'sale_price': 19.00},
],
},
{
'code': 'SO00004',
'title': u'劳保用品集中采购',
'order_date': datetime.date(2025, 5, 20),
'deliver_date': datetime.date(2025, 5, 28),
'contact': u'赵主任',
'phone': '13600136004',
'deliver_address': u'安徽省合肥市蜀山区望江西路666号',
'description': u'车间一线员工劳保手套补充采购。',
'status': '9',
'discount_amount': 0,
'items': [
{'material_code': 'IT00005', 'cnt': 1000, 'sale_price': 5.50},
],
},
]
def load_samples():
partner = Partner.objects.filter(partner_type='C').first()
if not partner:
print(u'错误: 未找到客户类型合作伙伴,请先维护基础数据。')
return
user = User.objects.filter(username='chengcai').first() or User.objects.first()
org = Organization.objects.first()
creator = user.username if user else 'system'
today = datetime.date.today()
created = 0
skipped = 0
with transaction.atomic():
for data in SAMPLE_ORDERS:
if SaleOrder.objects.filter(code=data['code']).exists():
print(u'跳过已存在订单: %s' % data['code'])
skipped += 1
continue
order = SaleOrder(
code=data['code'],
title=data['title'],
order_date=data['order_date'],
deliver_date=data['deliver_date'],
partner=partner,
org=org,
user=user,
contact=data['contact'],
phone=data['phone'],
deliver_address=data['deliver_address'],
description=data['description'],
invoice_type='10',
status=data['status'],
discount_amount=data['discount_amount'],
amount=0,
begin=today,
end=datetime.date(9999, 12, 31),
creator=creator,
modifier=creator,
)
order.save()
for item_data in data['items']:
material = Material.objects.filter(code=item_data['material_code']).first()
if not material:
print(u'警告: 物料 %s 不存在,已跳过' % item_data['material_code'])
continue
item = SaleItem(
master=order,
material=material,
cnt=item_data['cnt'],
sale_price=item_data['sale_price'],
tax='0.00',
)
item.save()
if data['discount_amount'] > 0:
order.save()
total = recalc_order_amount(order)
print(u'已创建: %s %s 金额=%s 状态=%s' % (
order.code, order.title, total, order.get_status_display()
))
created += 1
print(u'\n完成: 新增 %d 条,跳过 %d 条。' % (created, skipped))
if __name__ == '__main__':
load_samples()
+7 -4
View File
@@ -3,15 +3,15 @@
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-01 19:18+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"PO-Revision-Date: 2026-06-10 12:00+0800\n"
"Last-Translator: \n"
"Language-Team: zh_Hans\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -750,6 +750,9 @@ msgstr "提交"
msgid "AGREE"
msgstr "同意"
msgid "object id"
msgstr "对象 ID"
msgid "workflow instance"
msgstr "工作流实例"
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -49,6 +49,7 @@ INSTALLED_APPS = (
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -89,6 +90,7 @@ DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': os.environ.get('DATABASE_HOST', 'localhost'),
'PORT': os.environ.get('DATABASE_PORT', '3306'),
'NAME': os.environ.get('DATABASE_NAME', 'mis'),
'USER': os.environ.get('DATABASE_USER', 'root'),
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'root'),
@@ -99,7 +101,7 @@ DATABASES = {
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'zh-CN'
LANGUAGE_CODE = 'zh-hans'
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
+984
View File
@@ -0,0 +1,984 @@
/* 智捷ERP SmartJet ERP - 1:1 High-Fidelity UI System v4.0 */
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
:root {
--primary: #6366f1;
--primary-light: #818cf8;
--primary-bg: #f5f3ff;
--slate-50: #f8fafc;
--slate-100: #f1f5f9;
--slate-200: #e2e8f0;
--slate-300: #cbd5e1;
--slate-400: #94a3b8;
--slate-500: #64748b;
--slate-600: #475569;
--slate-700: #334155;
--slate-800: #1e293b;
--slate-900: #0f172a;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--info: #3b82f6;
--sidebar-width: 240px;
--sidebar-collapsed-width: 0px;
--topbar-height: 64px;
--radius-lg: 16px;
--radius-md: 10px;
--shadow-card: 0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
background-color: #f1f5f9 !important;
color: var(--slate-800) !important;
font-family: 'Plus Jakarta Sans', -apple-system, sans-serif !important;
margin: 0 !important;
padding: 0 !important;
overflow-x: hidden;
}
#container {
display: flex !important;
min-height: 100vh;
min-width: 0 !important;
max-width: 100vw;
width: 100%;
overflow-x: hidden;
}
/* --- SIDEBAR --- */
#header {
width: var(--sidebar-width) !important;
height: 100vh !important;
position: fixed !important;
left: 0; top: 0;
background: #ffffff !important;
border-right: 1px solid var(--slate-200) !important;
padding: 0 !important;
display: flex !important;
flex-direction: column !important;
z-index: 1000 !important;
color: var(--slate-800) !important;
line-height: normal !important;
overflow: visible !important;
}
/* 覆盖 Django admin 默认深色顶栏链接样式(白字导致侧栏文字不可见) */
#header a:link,
#header a:visited {
color: inherit !important;
text-decoration: none !important;
}
#header a:focus,
#header a:hover {
text-decoration: none !important;
}
.brand-section {
padding: 24px;
display: flex;
align-items: center;
}
.brand-logo {
width: 32px;
height: 32px;
background: linear-gradient(135deg, #6366f1, #a855f7);
border-radius: 8px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 800;
}
.brand-text h1 {
font-size: 18px !important;
font-weight: 800 !important;
color: #1e293b !important;
margin: 0 !important;
line-height: 1 !important;
}
.brand-text span {
font-size: 11px;
color: var(--slate-400);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.sidebar-nav {
flex: 1;
padding: 0 16px;
overflow-y: auto;
}
.nav-group-label {
padding: 20px 12px 10px 12px;
color: var(--slate-400);
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
.nav-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: 12px;
color: var(--slate-600) !important;
text-decoration: none !important;
font-weight: 600;
font-size: 14px;
margin-bottom: 4px;
transition: all 0.2s;
}
.nav-item:hover,
.nav-item:focus {
background: var(--slate-50);
color: var(--primary) !important;
}
.nav-item.active,
.nav-item.active:link,
.nav-item.active:visited {
background: var(--primary-bg) !important;
color: var(--primary) !important;
}
.nav-item i { margin-right: 12px; font-size: 18px; width: 20px; text-align: center; }
.nav-item .arrow { margin-left: auto; font-size: 10px; color: var(--slate-300); }
.sidebar-footer {
padding: 16px;
border-top: 1px solid var(--slate-100);
position: relative;
z-index: 1002;
flex-shrink: 0;
background: #ffffff;
}
.nav-item-btn {
width: 100%;
border: none;
background: transparent;
cursor: pointer;
font-family: inherit;
text-align: left;
}
button.nav-item-btn:hover,
button.nav-item-btn:focus {
background: var(--slate-50);
color: var(--primary) !important;
outline: none;
}
/* 侧栏收起 */
#header {
transition: transform 0.25s ease;
}
body.sidebar-collapsed #header {
transform: translateX(-100%);
pointer-events: none;
}
body.sidebar-collapsed #main {
margin-left: 0 !important;
max-width: 100vw !important;
}
/* --- TOPBAR --- */
#main {
flex: 1 1 auto !important;
margin-left: var(--sidebar-width) !important;
width: auto !important;
min-width: 0 !important;
max-width: calc(100vw - var(--sidebar-width)) !important;
overflow-x: hidden;
position: relative;
z-index: 1;
}
.topbar {
height: var(--topbar-height);
background: #ffffff !important;
opacity: 1 !important;
display: flex;
align-items: center;
padding: 0 32px;
position: sticky;
top: 0;
z-index: 900;
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
}
.topbar-left { display: flex; align-items: center; flex: 1; }
.topbar-right {
display: flex;
align-items: center;
gap: 20px;
background: #ffffff !important;
opacity: 1 !important;
position: relative;
z-index: 901;
pointer-events: auto;
}
.breadcrumb-new {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--slate-400);
margin-left: 20px;
}
.breadcrumb-new span { color: var(--slate-800); font-weight: 600; }
.search-btn, .icon-btn {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--slate-500);
cursor: pointer;
transition: background 0.2s;
border: none;
background: transparent;
padding: 0;
font-size: 16px;
line-height: 1;
pointer-events: auto;
position: relative;
z-index: 1;
}
.search-btn:hover, .icon-btn:hover,
.search-btn:focus, .icon-btn:focus {
background: var(--slate-100);
outline: none;
}
.user-menu-wrap {
position: relative;
padding-left: 20px;
border-left: 1px solid var(--slate-200);
}
.user-profile {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 8px;
border: none;
border-radius: 10px;
background: #ffffff !important;
opacity: 1 !important;
cursor: pointer;
font-family: inherit;
pointer-events: auto;
}
.user-profile:hover,
.user-profile:focus {
background: var(--slate-50) !important;
outline: none;
}
.user-caret {
font-size: 10px;
color: var(--slate-400);
margin-left: 4px;
}
.user-dropdown {
display: none;
position: absolute;
top: calc(100% + 8px);
right: 0;
min-width: 140px;
background: #ffffff;
border: 1px solid var(--slate-200);
border-radius: 10px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
padding: 6px;
z-index: 1100;
}
.user-dropdown.open {
display: block;
}
.user-dropdown a {
display: block;
padding: 10px 14px;
border-radius: 8px;
color: var(--slate-700) !important;
text-decoration: none !important;
font-size: 13px;
font-weight: 600;
}
.user-dropdown a:hover {
background: var(--slate-50);
color: var(--primary) !important;
}
.avatar {
width: 36px;
height: 36px;
border-radius: 10px;
background: var(--slate-200);
flex-shrink: 0;
opacity: 1 !important;
}
.user-info { display: flex; flex-direction: column; background: transparent; }
.user-name { font-weight: 700; font-size: 13px; color: var(--slate-800) !important; opacity: 1 !important; }
.user-role { font-size: 11px; color: var(--slate-500) !important; opacity: 1 !important; }
/* --- CONTENT --- */
#content {
padding: 32px !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
position: relative;
z-index: 1;
}
.dashboard #content {
width: 100% !important;
max-width: 100% !important;
}
#content-main {
float: none !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
}
.dashboard #content > h1 {
display: none;
}
#content > h1 {
font-size: 22px;
font-weight: 700;
color: var(--slate-900);
margin: 0 0 24px;
padding-right: 120px;
}
.colM, .colMS {
max-width: 100%;
min-width: 0;
}
#changelist, #changelist-form, .results {
max-width: 100%;
overflow-x: auto;
}
/* --- STATS CARDS --- */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 24px;
margin-bottom: 32px;
width: 100%;
max-width: 100%;
}
.stat-card {
background: #ffffff;
padding: 24px;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
position: relative;
min-width: 0;
}
.stat-header { display: flex; justify-content: space-between; margin-bottom: 12px; }
.stat-icon-bg { width: 40px; height: 40px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 18px; }
.stat-info-icon { color: var(--slate-300); font-size: 14px; cursor: help; }
.stat-value { font-size: 24px; font-weight: 800; color: var(--slate-900); margin-bottom: 8px; }
.stat-label { font-size: 14px; color: var(--slate-500); font-weight: 500; }
.stat-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; border-top: 1px solid var(--slate-50); padding-top: 16px; }
.stat-trend { font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: 4px; }
.trend-up { color: var(--success); }
.trend-down { color: var(--danger); }
.sparkline { width: 60px; height: 30px; }
/* --- DATA MODULES --- */
.dashboard-main-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
gap: 24px;
width: 100%;
max-width: 100%;
}
.module-card {
background: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
display: flex;
flex-direction: column;
min-width: 0;
}
.card-title {
padding: 24px 32px;
font-size: 16px;
font-weight: 700;
color: var(--slate-900);
display: flex;
justify-content: space-between;
align-items: center;
}
/* --- TABLE STYLES --- */
.table-container { overflow-x: auto; max-width: 100%; padding: 0 12px 12px 12px; }
table { width: 100%; max-width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 12px 20px; font-size: 12px; color: var(--slate-400); font-weight: 600; border-bottom: 1px solid var(--slate-100); }
td { padding: 16px 20px; font-size: 13px; color: var(--slate-700); border-bottom: 1px solid var(--slate-50); }
tr:last-child td { border: none; }
.badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 700;
}
.badge-new { background: #eef2ff; color: #6366f1; }
.badge-audit { background: #ecfdf5; color: #10b981; }
.badge-update { background: #eff6ff; color: #3b82f6; }
.badge-submit { background: #fff7ed; color: #f97316; }
/* --- TASK LIST --- */
.task-list { padding: 0 24px 24px 24px; }
.task-item {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 16px;
border-radius: 12px;
margin-bottom: 12px;
background: #ffffff;
border: 1px solid var(--slate-50);
transition: all 0.2s;
}
.task-item:hover { border-color: var(--slate-200); background: var(--slate-50); }
.task-icon { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 16px; color: white; }
.task-content { flex: 1; }
.task-name { font-size: 13px; font-weight: 700; color: var(--slate-800); }
.task-info { font-size: 11px; color: var(--slate-400); margin-top: 2px; }
.task-time { font-size: 11px; color: var(--slate-400); }
.priority-tag { font-size: 10px; font-weight: 800; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; margin-top: 8px; display: inline-block; }
.p-urgent { background: #fef2f2; color: #ef4444; }
.p-high { background: #fff7ed; color: #f97316; }
.p-medium { background: #fffbeb; color: #f59e0b; }
.p-low { background: #f0fdf4; color: #10b981; }
.view-all { text-align: center; padding: 20px; border-top: 1px solid var(--slate-50); }
.view-all a { color: var(--primary); text-decoration: none; font-size: 13px; font-weight: 700; }
/* --- ADMIN FORM ACTIONS --- */
#content-main {
position: relative;
padding-bottom: 24px;
}
.object-tools {
float: none !important;
position: absolute !important;
top: 32px;
right: 32px;
margin: 0 !important;
padding: 0 !important;
font-size: 12px !important;
font-weight: 600 !important;
opacity: 1 !important;
z-index: 5;
pointer-events: auto;
}
.object-tools li {
height: auto !important;
margin-left: 0 !important;
}
.object-tools a:link,
.object-tools a:visited,
.object-tools a.historylink {
display: inline-flex !important;
align-items: center;
float: none !important;
padding: 8px 16px !important;
background: var(--slate-600) !important;
color: #ffffff !important;
opacity: 1 !important;
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
text-transform: none !important;
letter-spacing: 0 !important;
text-decoration: none !important;
}
.object-tools a:hover,
.object-tools a:focus {
background: var(--slate-700) !important;
color: #ffffff !important;
opacity: 1 !important;
}
.submit-row {
display: flex !important;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 10px;
overflow: visible !important;
padding: 16px 20px !important;
margin: 24px 0 0 !important;
background: #ffffff !important;
border: 1px solid var(--slate-200) !important;
border-radius: var(--radius-md) !important;
box-shadow: var(--shadow-card);
}
.submit-row p {
margin: 0 !important;
}
.submit-row p.deletelink-box {
float: none !important;
margin-right: auto !important;
}
.submit-row a.deletelink {
display: inline-flex !important;
align-items: center;
justify-content: center;
height: auto !important;
min-height: 38px;
line-height: 1.4 !important;
padding: 10px 20px !important;
border-radius: 8px !important;
color: #ffffff !important;
background: var(--danger) !important;
text-decoration: none !important;
font-weight: 600;
font-size: 13px;
}
.submit-row a.deletelink:hover,
.submit-row a.deletelink:focus {
background: #dc2626 !important;
}
.submit-row input,
.submit-row a.button,
.submit-row .button {
height: auto !important;
min-height: 38px;
line-height: 1.4 !important;
padding: 10px 20px !important;
margin: 0 !important;
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
text-transform: none !important;
cursor: pointer;
}
.submit-row input.default {
background: var(--primary) !important;
border-color: var(--primary) !important;
color: #ffffff !important;
}
.submit-row input[name="_addanother"],
.submit-row input[name="_continue"],
.submit-row input[name="_saveasnew"] {
background: #ffffff !important;
color: var(--slate-700) !important;
border: 1px solid var(--slate-300) !important;
}
/* 双栏选择器 */
.selector {
max-width: 100%;
}
.selector .selector-available h2,
.selector .selector-chosen h2 {
font-size: 13px;
font-weight: 600;
color: var(--slate-700);
}
.selector select {
max-width: 100%;
}
.selector .selector-filter label {
font-size: 12px;
color: var(--slate-500);
}
/* 表单模块 */
.module h2, fieldset.module h2 {
font-size: 14px;
font-weight: 700;
color: var(--slate-800);
background: var(--slate-50) !important;
border-bottom: 1px solid var(--slate-200);
}
.form-row label {
color: var(--slate-700) !important;
font-weight: 600;
}
/* Hide messy native elements */
#footer, .breadcrumbs, #content-related { display: none !important; }
@media (max-width: 1280px) {
.stats-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dashboard-main-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: 1fr;
}
#content {
padding: 20px !important;
}
.topbar {
padding: 0 16px;
}
}
/* --- LOGIN PAGE --- */
body.login-page {
margin: 0;
padding: 0;
min-height: 100vh;
background: var(--slate-100);
overflow-x: hidden;
}
.login-shell {
display: flex;
min-height: 100vh;
width: 100%;
}
.login-brand {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 48px;
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #a855f7 100%);
color: #ffffff;
position: relative;
overflow: hidden;
}
.login-brand::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at 20% 80%, rgba(255,255,255,0.12) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255,255,255,0.08) 0%, transparent 40%);
pointer-events: none;
}
.login-brand-inner {
position: relative;
max-width: 420px;
z-index: 1;
}
.login-brand-logo {
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.2);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
margin-bottom: 24px;
backdrop-filter: blur(4px);
}
.login-brand h1 {
font-size: 36px;
font-weight: 800;
margin: 0 0 8px;
letter-spacing: -0.02em;
}
.login-brand-tagline {
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.1em;
opacity: 0.8;
margin: 0 0 20px;
}
.login-brand-desc {
font-size: 16px;
opacity: 0.9;
margin: 0 0 32px;
line-height: 1.6;
}
.login-brand-features {
list-style: none;
padding: 0;
margin: 0;
}
.login-brand-features li {
padding: 10px 0;
padding-left: 24px;
position: relative;
font-size: 14px;
opacity: 0.9;
}
.login-brand-features li::before {
content: '✓';
position: absolute;
left: 0;
font-weight: 700;
}
.login-panel {
flex: 0 0 480px;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
background: var(--slate-50);
}
.login-card {
width: 100%;
max-width: 400px;
background: #ffffff;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
padding: 40px 36px;
border: 1px solid var(--slate-200);
}
.login-card-header {
margin-bottom: 28px;
}
.login-card-header h2 {
font-size: 24px;
font-weight: 800;
color: var(--slate-900);
margin: 0 0 8px;
}
.login-card-header p {
font-size: 14px;
color: var(--slate-500);
margin: 0;
}
.login-error {
background: #fef2f2;
color: #dc2626;
padding: 12px 14px;
border-radius: 8px;
font-size: 13px;
margin-bottom: 20px;
border: 1px solid #fecaca;
}
.login-form {
margin-bottom: 24px;
}
.login-field {
margin-bottom: 20px;
}
.login-field label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--slate-700);
margin-bottom: 8px;
}
.login-field input[type="text"],
.login-field input[type="password"] {
width: 100%;
padding: 12px 14px;
font-size: 14px;
font-family: inherit;
border: 1px solid var(--slate-300);
border-radius: var(--radius-md);
background: #ffffff;
color: var(--slate-800);
box-sizing: border-box;
transition: border-color 0.2s, box-shadow 0.2s;
}
.login-field input[type="text"]:focus,
.login-field input[type="password"]:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-bg);
}
.login-submit {
width: 100%;
padding: 14px;
margin-top: 8px;
font-size: 15px;
font-weight: 700;
font-family: inherit;
color: #ffffff;
background: linear-gradient(135deg, var(--primary), #7c3aed);
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: opacity 0.2s, transform 0.15s;
}
.login-submit:hover {
opacity: 0.92;
transform: translateY(-1px);
}
.login-submit:active {
transform: translateY(0);
}
.login-demo {
background: var(--primary-bg);
border: 1px solid #e0e7ff;
border-radius: var(--radius-md);
padding: 16px;
margin-bottom: 20px;
}
.login-demo-title {
font-size: 12px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.login-demo-row {
display: flex;
align-items: center;
gap: 12px;
font-size: 13px;
color: var(--slate-600);
margin-bottom: 4px;
}
.login-demo-row span {
min-width: 48px;
color: var(--slate-500);
}
.login-demo-row code {
font-family: 'Consolas', monospace;
background: rgba(255, 255, 255, 0.8);
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
color: var(--slate-800);
}
.login-demo-divider {
height: 1px;
background: #e0e7ff;
margin: 12px 0;
}
.login-footer-link {
text-align: center;
}
.login-footer-link a {
font-size: 12px;
color: var(--slate-400);
text-decoration: none;
}
.login-footer-link a:hover {
color: var(--primary);
}
@media (max-width: 900px) {
.login-shell {
flex-direction: column;
}
.login-brand {
flex: none;
padding: 32px 24px;
}
.login-brand-inner {
max-width: 100%;
text-align: center;
}
.login-brand-logo {
margin-left: auto;
margin-right: auto;
}
.login-brand-features {
display: none;
}
.login-panel {
flex: 1;
padding: 24px 16px 32px;
}
}
+86
View File
@@ -0,0 +1,86 @@
(function () {
'use strict';
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function toggleSidebar() {
var collapsed = document.body.classList.toggle('sidebar-collapsed');
try {
localStorage.setItem('erp-sidebar-collapsed', collapsed ? '1' : '0');
} catch (e) {}
updateCollapseLabel();
}
function updateCollapseLabel() {
var btn = document.getElementById('sidebarCollapseBtn');
if (!btn) {
return;
}
var collapsed = document.body.classList.contains('sidebar-collapsed');
btn.innerHTML = collapsed
? '<i>→</i> 展开菜单'
: '<i>←</i> 收起菜单';
}
function restoreSidebarState() {
try {
if (localStorage.getItem('erp-sidebar-collapsed') === '1') {
document.body.classList.add('sidebar-collapsed');
}
} catch (e) {}
updateCollapseLabel();
}
function toggleUserMenu() {
var menu = document.getElementById('userDropdown');
if (!menu) {
return;
}
var open = menu.classList.toggle('open');
if (open) {
document.addEventListener('click', closeUserMenuOutside);
} else {
document.removeEventListener('click', closeUserMenuOutside);
}
}
function closeUserMenuOutside(e) {
var menu = document.getElementById('userDropdown');
var profile = document.getElementById('userProfileToggle');
if (!menu || !profile) {
return;
}
if (!menu.contains(e.target) && !profile.contains(e.target)) {
menu.classList.remove('open');
document.removeEventListener('click', closeUserMenuOutside);
}
}
function bindClick(id, handler) {
var el = document.getElementById(id);
if (el) {
el.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
handler(e);
});
}
}
ready(function () {
restoreSidebarState();
bindClick('topbarMenuBtn', toggleSidebar);
bindClick('sidebarCollapseBtn', toggleSidebar);
bindClick('topbarNotifyBtn', function () {
window.location.href = '/admin/workflow/todolist/';
});
bindClick('userProfileToggle', toggleUserMenu);
});
})();
+88 -62
View File
@@ -4,6 +4,7 @@
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}" />
<link rel="stylesheet" type="text/css" href="{% static "css/modern_ui.css" %}" />
{% block extrastyle %}{% endblock %}
<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="{% block stylesheet_ie %}{% static "admin/css/ie.css" %}{% endblock %}" /><![endif]-->
{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}" />{% endif %}
@@ -20,77 +21,102 @@
<div id="container">
{% if not is_popup %}
<!-- Header -->
<!-- Header (Sidebar) -->
<div id="header">
<div id="branding">
{% block branding %}{% endblock %}
<div class="brand-section">
<div class="brand-logo"></div>
<div class="brand-text">
<h1>智捷ERP</h1>
<span>SmartJet ERP</span>
</div>
</div>
{% block usertools %}
{% if has_permission %}
<div id="user-tools">
<a href="http://code.xinmi.cloud/" target="_blank" style="color: #ffeb3b; font-weight: bold;">新觅源码库</a> /
{% block welcome-msg %}
{% trans 'Welcome,' %}
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
{% endblock %}
{% block userlinks %}
<a href="/admin/workflow/todolist">{% trans 'workflow todo' %}</a> /
{% if site_url %}
<a href="/admin/basedata/document">{% trans 'document' %}</a> /
{% endif %}
{% if user.is_active and user.is_staff %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
{% endif %}
{% endif %}
{% if user.has_usable_password %}
<a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
{% endif %}
<a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
{% endblock %}
<div class="sidebar-nav">
<a href="/admin/" class="nav-item active"><i>🏠</i> 控制面板</a>
<div class="nav-group-label">核心业务</div>
<a href="/admin/sale/saleorder/" class="nav-item"><i>📁</i> 销售管理 <span class="arrow">v</span></a>
<a href="/admin/purchase/purchaseorder/" class="nav-item"><i>🛒</i> 采购管理 <span class="arrow">v</span></a>
<a href="/admin/invent/inventory/" class="nav-item"><i>📦</i> 库存管理 <span class="arrow">v</span></a>
<div class="nav-group-label">组织人力</div>
<a href="/admin/organ/organization/" class="nav-item"><i>🏢</i> 组织架构</a>
<a href="/admin/basedata/employee/" class="nav-item"><i>👥</i> 员工档案</a>
<div class="nav-group-label">流程协同</div>
<a href="/admin/workflow/instance/" class="nav-item"><i>🛡️</i> 审批中心</a>
</div>
{% endif %}
{% endblock %}
{% block nav-global %}{% endblock %}
<div class="sidebar-footer">
<a href="/admin/syscfg/site/" class="nav-item"><i>⚙️</i> 设置</a>
<button type="button" id="sidebarCollapseBtn" class="nav-item nav-item-btn"><i></i> 收起菜单</button>
</div>
{% block usertools %}{% endblock %}
</div>
<!-- END Header -->
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
{% if title %} &rsaquo; {{ title }}{% endif %}
</div>
{% endblock %}
{% endif %}
{% block messages %}
{% if messages %}
<ul class="messagelist">{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li>
{% endfor %}</ul>
{% endif %}
{% endblock messages %}
<!-- Content -->
<div id="content" class="{% block coltype %}colM{% endblock %}">
{% block pretitle %}{% endblock %}
{% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
{% block content %}
{% block object-tools %}{% endblock %}
{{ content }}
{% endblock %}
{% block sidebar %}{% endblock %}
<br class="clear" />
</div>
<!-- END Content -->
{% block footer %}<div id="footer">
<div style="text-align: center; padding: 10px;">
<a href="http://code.xinmi.cloud/" target="_blank" style="color: #666; text-decoration: none;">智捷ERP - 新觅源码库</a>
<div id="main" {% if is_popup %}style="margin-left: 0; width: 100%;"{% endif %}>
{% if not is_popup %}
<div class="topbar">
<div class="topbar-left">
<button type="button" class="icon-btn" id="topbarMenuBtn" title="切换菜单"></button>
<div class="breadcrumb-new">首页 > <span>控制面板</span></div>
</div>
<div class="topbar-right">
<button type="button" class="icon-btn" id="topbarNotifyBtn" title="待办任务">🔔</button>
<div class="user-menu-wrap">
<button type="button" class="user-profile" id="userProfileToggle" title="用户菜单">
<div class="avatar" style="background: url('https://ui-avatars.com/api/?name={{ user.get_short_name|default:"管理员" }}&background=6366f1&color=fff') no-repeat center center; background-size: cover;"></div>
<div class="user-info">
<span class="user-name">{{ user.get_short_name|default:"管理员" }}</span>
<span class="user-role">系统管理员</span>
</div>
<span class="user-caret"></span>
</button>
<div class="user-dropdown" id="userDropdown">
<a href="{% url 'admin:password_change' %}">修改密码</a>
<a href="{% url 'admin:logout' %}">退出登录</a>
</div>
</div>
</div>
</div>
</div>{% endblock %}
{% endif %}
{% block messages %}
{% if messages %}
<ul class="messagelist">{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li>
{% endfor %}</ul>
{% endif %}
{% endblock messages %}
<!-- Content -->
<div id="content" class="{% block coltype %}colM{% endblock %}">
{% block pretitle %}{% endblock %}
{% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
{% block content %}
{% block object-tools %}{% endblock %}
{{ content }}
{% endblock %}
{% block sidebar %}{% endblock %}
<br class="clear" />
</div>
<!-- END Content -->
{% if not is_popup %}
{% block footer %}
<div id="footer">
<div style="text-align: center; padding: 10px;">
<a href="http://code.xinmi.cloud/" target="_blank" style="color: #666; text-decoration: none;">智捷ERP - 新觅源码库</a>
</div>
</div>
{% endblock %}
{% endif %}
</div>
</div>
<!-- END Container -->
<script type="text/javascript" src="{% static "js/modern_ui.js" %}"></script>
</body>
</html>
+151 -99
View File
@@ -1,110 +1,162 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_static %}
{% block extrastyle %}{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
{% for app in maxi_app_list %}
| <a href="{{ app.app_url }}">{{ app.name }}</a>
{% endfor %}
</div>
{% endblock %}
{% block content %}
<div id="content-main">
{% if maxi_app_list %}
{% for app in maxi_app_list %}
<!-- Row 1: Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<div class="stat-icon-bg" style="background: #eef2ff; color: #6366f1;"></div>
<span class="stat-info-icon"></span>
</div>
<div class="stat-label">销售总额</div>
<div class="stat-value">¥ 8,231,078</div>
<div class="stat-footer">
<span class="stat-trend trend-up">较上月 ▲ 12.6%</span>
<svg class="sparkline" viewBox="0 0 100 30"><path d="M0,25 Q15,5 30,20 T60,10 T100,25" fill="none" stroke="#6366f1" stroke-width="2"/></svg>
</div>
</div>
<div class="stat-card">
<div class="stat-header">
<div class="stat-icon-bg" style="background: #eff6ff; color: #3b82f6;">📋</div>
<span class="stat-info-icon"></span>
</div>
<div class="stat-label">待办审批</div>
<div class="stat-value">32</div>
<div class="stat-footer">
<span class="stat-trend trend-down" style="color: #ef4444;">较昨日 ▲ 8</span>
<svg class="sparkline" viewBox="0 0 100 30"><path d="M0,15 L20,25 L40,10 L60,20 L80,5 L100,15" fill="none" stroke="#3b82f6" stroke-width="2"/></svg>
</div>
</div>
<div class="stat-card">
<div class="stat-header">
<div class="stat-icon-bg" style="background: #fff7ed; color: #f97316;">📦</div>
<span class="stat-info-icon"></span>
</div>
<div class="stat-label">库存预警</div>
<div class="stat-value">18</div>
<div class="stat-footer">
<span class="stat-trend trend-down" style="color: #ef4444;">较昨日 ▲ 5</span>
<svg class="sparkline" viewBox="0 0 100 30"><path d="M0,25 L30,5 L70,25 L100,10" fill="none" stroke="#f97316" stroke-width="2"/></svg>
</div>
</div>
<div class="stat-card">
<div class="stat-header">
<div class="stat-icon-bg" style="background: #f0fdf4; color: #10b981;">🖥️</div>
<span class="stat-info-icon"></span>
</div>
<div class="stat-label">系统在线</div>
<div class="stat-value">98.6%</div>
<div class="stat-footer">
<span class="stat-trend trend-up">较昨日 ▲ 0.8%</span>
<svg class="sparkline" viewBox="0 0 100 30"><path d="M0,20 L25,15 L50,22 L75,10 L100,12" fill="none" stroke="#10b981" stroke-width="2"/></svg>
</div>
</div>
</div>
<div class="app-{{ app.app_label }} module">
<table>
<caption>
<a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">{{ app.name }}</a>
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.admin_url %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</table>
<!-- Row 2: Main Grid -->
<div class="dashboard-main-grid">
<!-- Operation Logs -->
<div class="module-card">
<div class="card-title">最近操作日志</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>时间</th>
<th>操作人</th>
<th>模块</th>
<th>操作类型</th>
<th>操作内容</th>
<th>IP地址</th>
</tr>
</thead>
<tbody>
<tr>
<td>2025-05-22 10:24:31</td>
<td>张伟</td>
<td>销售管理</td>
<td><span class="badge badge-new">新增</span></td>
<td>新增销售订单 SO-20250522-001</td>
<td>192.168.1.100</td>
</tr>
<tr>
<td>2025-05-22 10:18:44</td>
<td>李娜</td>
<td>采购管理</td>
<td><span class="badge badge-audit">审核</span></td>
<td>审核采购订单 PO-20250522-045</td>
<td>192.168.1.101</td>
</tr>
<tr>
<td>2025-05-22 09:56:12</td>
<td>王强</td>
<td>库存管理</td>
<td><span class="badge badge-update">更新</span></td>
<td>更新库存调拔单 TB-20250522-009</td>
<td>192.168.1.102</td>
</tr>
<tr>
<td>2025-05-22 09:41:07</td>
<td>张伟</td>
<td>审批中心</td>
<td><span class="badge badge-submit">提交</span></td>
<td>提交费用报销单 FB-20250522-008</td>
<td>192.168.1.100</td>
</tr>
</tbody>
</table>
</div>
<div style="padding: 20px 32px; border-top: 1px solid var(--slate-50); display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: var(--slate-400);">
<span>共 120 条记录</span>
<div style="display: flex; gap: 8px;">
<span style="width: 24px; height: 24px; background: var(--primary); color: white; display: flex; align-items: center; justify-content: center; border-radius: 4px;">1</span>
<span style="width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">2</span>
<span style="width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">3</span>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
{% block sidebar %}
<div id="content-related" style="width:18em">
<div class="module">
<h2><a href="workflow/todolist" style="float:right">更多</a>我的待办</h2>
{% if not todolist %}
<p>无待办任务</p>
{% else %}
<ul class="actionlist">
{% for todo in todolist%}
<li class="changelink">
{{todo.href}}<br/>
<span class="mini quiet">{{todo.submitter}} {{todo.start_time}}</span>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="module" id="recent-actions-module">
<h2>{% trans 'Recent Actions' %}</h2>
<h3>{% trans 'My Actions' %}</h3>
{% load log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
<p>{% trans 'None available' %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
{% if entry.is_deletion or not entry.get_admin_url %}
{{ entry.object_repr }}
{% else %}
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
{% endif %}
<br/>
{% if entry.content_type %}
<span class="mini quiet">{% filter capfirst %}{{ entry.content_type }}{% endfilter %}</span>
{% else %}
<span class="mini quiet">{% trans 'Unknown content' %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
<!-- Task Sidebar -->
<div class="module-card">
<div class="card-title">
待办任务
<a href="#" style="font-size: 12px; color: var(--primary); text-decoration: none;">更多 ></a>
</div>
<div class="task-list">
<div class="task-item">
<div class="task-icon" style="background: #3b82f6;">📄</div>
<div class="task-content">
<div class="task-name">费用报销单审批</div>
<div class="task-info">报销人:张伟</div>
<span class="priority-tag p-urgent">紧急</span>
</div>
<div class="task-time">10分钟前</div>
</div>
<div class="task-item">
<div class="task-icon" style="background: #f97316;">🛒</div>
<div class="task-content">
<div class="task-name">采购订单审批</div>
<div class="task-info">单号:PO-20250522-047</div>
<span class="priority-tag p-high"></span>
</div>
<div class="task-time">30分钟前</div>
</div>
<div class="task-item">
<div class="task-icon" style="background: #10b981;">💰</div>
<div class="task-content">
<div class="task-name">销售订单审批</div>
<div class="task-info">单号:SO-20250522-004</div>
<span class="priority-tag p-medium"></span>
</div>
<div class="task-time">1小时前</div>
</div>
</div>
<div class="view-all">
<a href="/admin/workflow/todolist/">查看全部待办 →</a>
</div>
</div>
</div>
</div>
{% endblock %}
+65 -130
View File
@@ -1,140 +1,75 @@
{% extends "admin/base_site.html" %}
{% extends "admin/login_base.html" %}
{% load i18n admin_static %}
{% block extrastyle %}{{ block.super }}
<style>
body {
background: url('https://images.unsplash.com/photo-1497215728101-856f4ea42174?ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80') no-repeat center center fixed;
background-size: cover;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
#container {
background: transparent;
width: 100%;
display: flex;
justify-content: center;
}
#content {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
width: 100%;
max-width: 400px;
padding: 40px;
backdrop-filter: blur(8px);
margin: 0;
}
.login #header, .login .breadcrumbs, .login #footer { display: none; }
#content-title { display: none; }
.login .form-row { border: none; padding: 12px 0; }
.login label { display: block; margin-bottom: 8px; color: #444; font-weight: 600; font-size: 14px; }
.login input[type=text], .login input[type=password] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
font-size: 14px;
transition: border-color 0.3s;
}
.login input[type=text]:focus, .login input[type=password]:focus {
border-color: #007bff;
outline: none;
}
.submit-row { margin-top: 24px; text-align: center; }
.submit-row input {
background: #007bff;
color: white;
padding: 12px 0;
width: 100%;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background 0.3s, transform 0.2s;
}
.submit-row input:hover {
background: #0056b3;
transform: translateY(-1px);
}
.submit-row input:active {
transform: translateY(0);
}
.default-account {
margin-top: 30px;
padding: 15px;
background: #f0f7ff;
border-left: 4px solid #007bff;
border-radius: 4px;
font-size: 13px;
color: #555;
line-height: 1.6;
}
.brand-logo {
text-align: center;
margin-bottom: 30px;
}
.brand-logo h1 {
font-size: 28px;
color: #333;
margin: 0;
letter-spacing: 1px;
}
.errornote {
background: #fff5f5;
color: #e53e3e;
padding: 10px;
border-radius: 4px;
font-size: 13px;
margin-bottom: 20px;
border: 1px solid #feb2b2;
}
</style>
{% endblock %}
{% block content %}
<div id="content-main">
<div class="brand-logo">
<h1>智捷ERP</h1>
<p style="color: #666; margin-top: 5px;">高效管理 · 企业互联</p>
<div class="login-shell">
<div class="login-brand">
<div class="login-brand-inner">
<div class="login-brand-logo"></div>
<h1>智捷ERP</h1>
<p class="login-brand-tagline">SmartJet ERP</p>
<p class="login-brand-desc">高效管理 · 智能协同 · 企业互联</p>
<ul class="login-brand-features">
<li>销售 / 采购 / 库存一体化</li>
<li>组织架构与审批流程</li>
<li>数据驱动经营决策</li>
</ul>
</div>
</div>
{% if form.errors and not form.non_field_errors %}
<p class="errornote">
{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{% endif %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="errornote">
{{ error }}
</p>
{% endfor %}
{% endif %}
<div class="login-panel">
<div class="login-card">
<div class="login-card-header">
<h2>欢迎登录</h2>
<p>请输入您的账号和密码</p>
</div>
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
{{ form.username.label_tag }} {{ form.username }}
</div>
<div class="form-row">
{{ form.password.label_tag }} {{ form.password }}
<input type="hidden" name="next" value="{{ next }}" />
</div>
<div class="submit-row">
<input type="submit" value="{% trans 'Log in' %}" />
</div>
</form>
{% if form.errors and not form.non_field_errors %}
<div class="login-error">
{% if form.errors.items|length == 1 %}请修正以下错误。{% else %}请修正以下错误。{% endif %}
</div>
{% endif %}
<div class="default-account">
<strong style="color: #007bff;">演示账号</strong><br>
用户名:admin<br>
&nbsp;&nbsp;&nbsp;码:admin
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="login-error">{{ error }}</div>
{% endfor %}
{% endif %}
{% if user.is_authenticated %}
<div class="login-error">
您已以 {{ user.get_username }} 登录,但无权访问此页面。请使用其他账号登录。
</div>
{% endif %}
<form action="{{ app_path }}" method="post" id="login-form" class="login-form">
{% csrf_token %}
<div class="login-field">
<label for="id_username">用户名</label>
{{ form.username }}
</div>
<div class="login-field">
<label for="id_password">密码</label>
{{ form.password }}
<input type="hidden" name="next" value="{{ next }}" />
</div>
<button type="submit" class="login-submit">登 录</button>
</form>
<div class="login-demo">
<div class="login-demo-title">演示账号</div>
<div class="login-demo-row">
<span>用户名</span><code>xinmi_admin</code>
</div>
<div class="login-demo-row">
<span>&nbsp;&nbsp;&nbsp;</span><code>admin123456</code>
</div>
</div>
<div class="login-footer-link">
<a href="http://code.xinmi.cloud/" target="_blank" rel="noopener">智捷ERP · 新觅源码库</a>
</div>
</div>
</div>
</div>
{% endblock %}
+13
View File
@@ -0,0 +1,13 @@
{% load i18n admin_static %}<!DOCTYPE html>
<html lang="zh-hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}登录 | 智捷ERP{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% static "css/modern_ui.css" %}" />
{% block extrastyle %}{% endblock %}
</head>
<body class="login-page">
{% block content %}{% endblock %}
</body>
</html>
+1 -1
View File
@@ -109,7 +109,7 @@ class Instance(models.Model):
index_weight = 3
code = models.CharField(_("code"),blank=True,null=True,max_length=const.DB_CHAR_CODE_10)
modal = models.ForeignKey(Modal,verbose_name=_("workflow model"))
object_id = models.PositiveIntegerField("object id")
object_id = models.PositiveIntegerField(_("object id"))
starter = models.ForeignKey(User,verbose_name=_("start user"),related_name="starter")
start_time = models.DateTimeField(_("start time"),auto_now_add=True)
approved_time = models.DateTimeField(_("approved time"),blank=True,null=True)