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 - **状态**success
- **开始时间**2026-6-10 09:51:31 - **开始时间**2026-6-10 09:51:31
- **完成时间** - **完成时间**
@@ -71,9 +71,9 @@
- **说明**:健康验证通过。Web 服务(6010)已成功启动并可以正常访问登录页面。 - **说明**:健康验证通过。Web 服务(6010)已成功启动并可以正常访问登录页面。
### 步骤 11:提交 ### 步骤 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-01 19:18+0800\n" "POT-Creation-Date: 2015-05-01 19:18+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2026-06-10 12:00+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: \n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: zh_Hans\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@@ -750,6 +750,9 @@ msgstr "提交"
msgid "AGREE" msgid "AGREE"
msgstr "同意" msgstr "同意"
msgid "object id"
msgstr "对象 ID"
msgid "workflow instance" msgid "workflow instance"
msgstr "工作流实例" msgstr "工作流实例"
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -49,6 +49,7 @@ INSTALLED_APPS = (
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -89,6 +90,7 @@ DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'HOST': os.environ.get('DATABASE_HOST', 'localhost'), 'HOST': os.environ.get('DATABASE_HOST', 'localhost'),
'PORT': os.environ.get('DATABASE_PORT', '3306'),
'NAME': os.environ.get('DATABASE_NAME', 'mis'), 'NAME': os.environ.get('DATABASE_NAME', 'mis'),
'USER': os.environ.get('DATABASE_USER', 'root'), 'USER': os.environ.get('DATABASE_USER', 'root'),
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'root'), 'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'root'),
@@ -99,7 +101,7 @@ DATABASES = {
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # 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')] 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> <head>
<title>{% block title %}{% endblock %}</title> <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="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}" />
<link rel="stylesheet" type="text/css" href="{% static "css/modern_ui.css" %}" />
{% block extrastyle %}{% endblock %} {% 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 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 %} {% 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"> <div id="container">
{% if not is_popup %} {% if not is_popup %}
<!-- Header --> <!-- Header (Sidebar) -->
<div id="header"> <div id="header">
<div id="branding"> <div class="brand-section">
{% block branding %}{% endblock %} <div class="brand-logo"></div>
<div class="brand-text">
<h1>智捷ERP</h1>
<span>SmartJet ERP</span>
</div>
</div> </div>
{% block usertools %}
{% if has_permission %} <div class="sidebar-nav">
<div id="user-tools"> <a href="/admin/" class="nav-item active"><i>🏠</i> 控制面板</a>
<a href="http://code.xinmi.cloud/" target="_blank" style="color: #ffeb3b; font-weight: bold;">新觅源码库</a> /
{% block welcome-msg %} <div class="nav-group-label">核心业务</div>
{% trans 'Welcome,' %} <a href="/admin/sale/saleorder/" class="nav-item"><i>📁</i> 销售管理 <span class="arrow">v</span></a>
<strong>{% firstof user.get_short_name user.get_username %}</strong>. <a href="/admin/purchase/purchaseorder/" class="nav-item"><i>🛒</i> 采购管理 <span class="arrow">v</span></a>
{% endblock %} <a href="/admin/invent/inventory/" class="nav-item"><i>📦</i> 库存管理 <span class="arrow">v</span></a>
{% block userlinks %}
<a href="/admin/workflow/todolist">{% trans 'workflow todo' %}</a> / <div class="nav-group-label">组织人力</div>
{% if site_url %} <a href="/admin/organ/organization/" class="nav-item"><i>🏢</i> 组织架构</a>
<a href="/admin/basedata/document">{% trans 'document' %}</a> / <a href="/admin/basedata/employee/" class="nav-item"><i>👥</i> 员工档案</a>
{% endif %}
{% if user.is_active and user.is_staff %} <div class="nav-group-label">流程协同</div>
{% url 'django-admindocs-docroot' as docsroot %} <a href="/admin/workflow/instance/" class="nav-item"><i>🛡️</i> 审批中心</a>
{% 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> </div>
{% endif %}
{% endblock %} <div class="sidebar-footer">
{% block nav-global %}{% endblock %} <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> </div>
<!-- END Header -->
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
{% if title %} &rsaquo; {{ title }}{% endif %}
</div>
{% endblock %}
{% endif %} {% endif %}
{% block messages %} <div id="main" {% if is_popup %}style="margin-left: 0; width: 100%;"{% endif %}>
{% if messages %} {% if not is_popup %}
<ul class="messagelist">{% for message in messages %} <div class="topbar">
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li> <div class="topbar-left">
{% endfor %}</ul> <button type="button" class="icon-btn" id="topbarMenuBtn" title="切换菜单"></button>
{% endif %} <div class="breadcrumb-new">首页 > <span>控制面板</span></div>
{% endblock messages %} </div>
<div class="topbar-right">
<!-- Content --> <button type="button" class="icon-btn" id="topbarNotifyBtn" title="待办任务">🔔</button>
<div id="content" class="{% block coltype %}colM{% endblock %}"> <div class="user-menu-wrap">
{% block pretitle %}{% endblock %} <button type="button" class="user-profile" id="userProfileToggle" title="用户菜单">
{% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %} <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>
{% block content %} <div class="user-info">
{% block object-tools %}{% endblock %} <span class="user-name">{{ user.get_short_name|default:"管理员" }}</span>
{{ content }} <span class="user-role">系统管理员</span>
{% endblock %} </div>
{% block sidebar %}{% endblock %} <span class="user-caret"></span>
<br class="clear" /> </button>
</div> <div class="user-dropdown" id="userDropdown">
<!-- END Content --> <a href="{% url 'admin:password_change' %}">修改密码</a>
<a href="{% url 'admin:logout' %}">退出登录</a>
{% block footer %}<div id="footer"> </div>
<div style="text-align: center; padding: 10px;"> </div>
<a href="http://code.xinmi.cloud/" target="_blank" style="color: #666; text-decoration: none;">智捷ERP - 新觅源码库</a> </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> </div>
<!-- END Container --> <!-- END Container -->
<script type="text/javascript" src="{% static "js/modern_ui.js" %}"></script>
</body> </body>
</html> </html>
+151 -99
View File
@@ -1,110 +1,162 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n admin_static %} {% 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 %} {% block content %}
<div id="content-main"> <div id="content-main">
{% if maxi_app_list %} <!-- Row 1: Stats Cards -->
{% for app in maxi_app_list %} <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"> <!-- Row 2: Main Grid -->
<table> <div class="dashboard-main-grid">
<caption> <!-- Operation Logs -->
<a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">{{ app.name }}</a> <div class="module-card">
</caption> <div class="card-title">最近操作日志</div>
{% for model in app.models %} <div class="table-container">
<tr class="model-{{ model.object_name|lower }}"> <table>
{% if model.admin_url %} <thead>
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> <tr>
{% else %} <th>时间</th>
<th scope="row">{{ model.name }}</th> <th>操作人</th>
{% endif %} <th>模块</th>
<th>操作类型</th>
{% if model.add_url %} <th>操作内容</th>
<td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td> <th>IP地址</th>
{% else %} </tr>
<td>&nbsp;</td> </thead>
{% endif %} <tbody>
<tr>
{% if model.admin_url %} <td>2025-05-22 10:24:31</td>
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td> <td>张伟</td>
{% else %} <td>销售管理</td>
<td>&nbsp;</td> <td><span class="badge badge-new">新增</span></td>
{% endif %} <td>新增销售订单 SO-20250522-001</td>
</tr> <td>192.168.1.100</td>
{% endfor %} </tr>
<tr>
</table> <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> </div>
{% endfor %} <!-- Task Sidebar -->
{% else %} <div class="module-card">
<p>{% trans "You don't have permission to edit anything." %}</p> <div class="card-title">
{% endif %} 待办任务
</div> <a href="#" style="font-size: 12px; color: var(--primary); text-decoration: none;">更多 ></a>
{% endblock %} </div>
<div class="task-list">
{% block sidebar %} <div class="task-item">
<div id="content-related" style="width:18em"> <div class="task-icon" style="background: #3b82f6;">📄</div>
<div class="module"> <div class="task-content">
<h2><a href="workflow/todolist" style="float:right">更多</a>我的待办</h2> <div class="task-name">费用报销单审批</div>
{% if not todolist %} <div class="task-info">报销人:张伟</div>
<p>无待办任务</p> <span class="priority-tag p-urgent">紧急</span>
{% else %} </div>
<ul class="actionlist"> <div class="task-time">10分钟前</div>
{% for todo in todolist%} </div>
<li class="changelink"> <div class="task-item">
{{todo.href}}<br/> <div class="task-icon" style="background: #f97316;">🛒</div>
<span class="mini quiet">{{todo.submitter}} {{todo.start_time}}</span> <div class="task-content">
</li> <div class="task-name">采购订单审批</div>
{% endfor %} <div class="task-info">单号:PO-20250522-047</div>
</ul> <span class="priority-tag p-high"></span>
{% endif %} </div>
</div> <div class="task-time">30分钟前</div>
<div class="module" id="recent-actions-module"> </div>
<h2>{% trans 'Recent Actions' %}</h2> <div class="task-item">
<h3>{% trans 'My Actions' %}</h3> <div class="task-icon" style="background: #10b981;">💰</div>
{% load log %} <div class="task-content">
{% get_admin_log 10 as admin_log for_user user %} <div class="task-name">销售订单审批</div>
{% if not admin_log %} <div class="task-info">单号:SO-20250522-004</div>
<p>{% trans 'None available' %}</p> <span class="priority-tag p-medium"></span>
{% else %} </div>
<ul class="actionlist"> <div class="task-time">1小时前</div>
{% for entry in admin_log %} </div>
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}"> </div>
{% if entry.is_deletion or not entry.get_admin_url %} <div class="view-all">
{{ entry.object_repr }} <a href="/admin/workflow/todolist/">查看全部待办 →</a>
{% else %} </div>
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a> </div>
{% 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 %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
+65 -130
View File
@@ -1,140 +1,75 @@
{% extends "admin/base_site.html" %} {% extends "admin/login_base.html" %}
{% load i18n admin_static %} {% 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 %} {% block content %}
<div id="content-main"> <div class="login-shell">
<div class="brand-logo"> <div class="login-brand">
<h1>智捷ERP</h1> <div class="login-brand-inner">
<p style="color: #666; margin-top: 5px;">高效管理 · 企业互联</p> <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> </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 %} <div class="login-panel">
{% for error in form.non_field_errors %} <div class="login-card">
<p class="errornote"> <div class="login-card-header">
{{ error }} <h2>欢迎登录</h2>
</p> <p>请输入您的账号和密码</p>
{% endfor %} </div>
{% endif %}
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %} {% if form.errors and not form.non_field_errors %}
<div class="form-row"> <div class="login-error">
{{ form.username.label_tag }} {{ form.username }} {% if form.errors.items|length == 1 %}请修正以下错误。{% else %}请修正以下错误。{% endif %}
</div> </div>
<div class="form-row"> {% endif %}
{{ 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>
<div class="default-account"> {% if form.non_field_errors %}
<strong style="color: #007bff;">演示账号</strong><br> {% for error in form.non_field_errors %}
用户名:admin<br> <div class="login-error">{{ error }}</div>
&nbsp;&nbsp;&nbsp;码:admin {% 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>
</div> </div>
{% endblock %} {% 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 index_weight = 3
code = models.CharField(_("code"),blank=True,null=True,max_length=const.DB_CHAR_CODE_10) code = models.CharField(_("code"),blank=True,null=True,max_length=const.DB_CHAR_CODE_10)
modal = models.ForeignKey(Modal,verbose_name=_("workflow model")) 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") starter = models.ForeignKey(User,verbose_name=_("start user"),related_name="starter")
start_time = models.DateTimeField(_("start time"),auto_now_add=True) start_time = models.DateTimeField(_("start time"),auto_now_add=True)
approved_time = models.DateTimeField(_("approved time"),blank=True,null=True) approved_time = models.DateTimeField(_("approved time"),blank=True,null=True)