Compare commits

..

17 Commits

Author SHA1 Message Date
635ba67876 调整Excel导出按钮颜色为蓝色 2026-05-26 17:00:31 +08:00
776ae36ede 添加联系人Excel导出功能和合规管理人分类 2026-05-26 16:50:54 +08:00
56ada564e0 更新数据库 2026-05-14 11:07:12 +08:00
2714d78f56 更新README文件并添加机构数量统计功能 2026-04-27 15:27:32 +08:00
0a35884a09 chore: 更新数据库文件 2026-02-02 17:55:08 +08:00
b6704519ec feat(ui): 优化多个页面的UI设计和交互体验
重构公共电子屏、分支机构信息、设备间图片和活动统计页面的UI设计
- 添加渐变背景、阴影和悬停效果提升视觉体验
- 使用图标增强信息展示和交互提示
- 改进表格和卡片布局提高可读性
- 统一各页面的设计风格和交互模式
- 添加CSS组件类支持新的UI效果
2026-01-28 12:30:14 +08:00
18314c9808 feat(huodong): 在活动类型中添加'变更'选项 2026-01-26 09:18:55 +08:00
2b1331d9f0 docs(update_provinces): 添加省份提取规则的第四条说明 2026-01-13 10:23:21 +08:00
4e9b5f5bd0 修改了pdf导出界面 2026-01-12 17:08:09 +08:00
dea365977e feat(报表导出): 增强分支机构报表导出功能
在Excel和PDF导出中增加省份分组显示,并添加统计信息
- 按省份分组显示分支机构数据
- 添加省份标题行和统计信息
- 优化表格样式和布局
- 在HTML模板中显示分支机构数量
2026-01-12 16:47:08 +08:00
51f3b9241c 调用脚本,重新更新了分支机构所在的营业部的信息 2026-01-12 16:37:10 +08:00
b99c2303e5 修改了分支机构排序的逻辑:省份优先 2026-01-12 16:04:22 +08:00
8eba3f0160 feat(联系人): 添加联系人信息导出为PDF功能
在联系人列表页面添加导出为PDF按钮,实现将筛选后的联系人信息导出为PDF文件。新增视图函数处理PDF生成逻辑,支持中文显示并保留原有筛选条件。
2026-01-12 15:48:51 +08:00
7c2095a52e docs: 添加项目README和依赖文件
添加项目README文档,包含系统功能、技术栈、项目结构、安装说明和使用文档
同时添加requirements.txt文件记录项目依赖
2026-01-12 14:12:30 +08:00
f86107ea5d chore: 从.gitignore中移除数据库相关文件
不再忽略db.sqlite3和*.sqlite3文件,这些文件现在将被纳入版本控制
2026-01-12 13:25:53 +08:00
26554c7f29 修改了年份显示的小样式 2026-01-05 11:47:06 +08:00
51286ae181 更新了年份显示和统计周期的显示 2026-01-05 11:40:08 +08:00
24 changed files with 4964 additions and 1304 deletions

4
.gitignore vendored
View File

@@ -2,10 +2,8 @@
__pycache__/
*.py[cod]
*$py.class
*.pyc
# Django stuff:
db.sqlite3
*.sqlite3
media/
# IDE related

189
README.md Normal file
View File

@@ -0,0 +1,189 @@
# 分支机构活动管理系统
这是一个基于Django开发的分支机构活动管理系统用于管理公司各分支机构的活动、人员、设备和预算等信息。
## 功能特点
- **分支机构管理**:维护分支机构的基本信息、联系方式、分类和状态
- **活动管理**:记录分支机构的新建、搬迁、装修、撤销等活动信息
- **联系人管理**:管理各分支机构的联系人信息及分类
- **设备间图片管理**:上传和管理分支机构设备间的图片
- **图纸管理**:上传和管理分支机构相关图纸
- **预算管理**:管理活动预算、设备预算和基础设施预算
- **终端设备管理**:管理视频终端等设备信息
- **公共屏幕管理**:管理分支机构的公共屏幕信息
## 技术栈
- **后端框架**Django
- **数据库**SQLite
- **前端技术**HTML、CSS、Tailwind CSS
- **表单处理**Django Forms
- **模板引擎**Django Templates
## 项目结构
```
fzjgact/
├── fzjgact/ # 项目配置目录
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── huodong/ # 主应用目录
│ ├── migrations/ # 数据库迁移文件
│ ├── static/ # 静态资源
│ ├── templates/ # 模板文件
│ ├── templatetags/ # 自定义模板标签
│ ├── __init__.py
│ ├── admin.py # 后台管理配置
│ ├── apps.py # 应用配置
│ ├── models.py # 数据模型
│ ├── serializers.py # 序列化器
│ ├── tests.py # 测试文件
│ ├── urls.py # 应用路由
│ └── views.py # 视图函数
├── db.sqlite3 # 数据库文件
├── manage.py # Django管理脚本
├── tailwind.config.js # Tailwind CSS配置
└── requirements.txt # 依赖文件(需创建)
```
## 安装和运行
### 1. 环境要求
- Python 3.7+
- Django 3.2+
- 其他依赖见requirements.txt
### 2. 安装步骤
1. **克隆项目**
```bash
git clone http://124.223.26.33:3000/xiaji/fzjg_local.git
cd 分支机构活动
```
2. **创建虚拟环境**
```bash
python -m venv venv
```
3. **激活虚拟环境**
- Windows:
```bash
venv\Scripts\activate
```
- Linux/Mac:
```bash
source venv/bin/activate
```
4. **安装依赖**
```bash
pip install -r requirements.txt
```
5. **初始化数据库**
```bash
cd fzjgact
python manage.py migrate
```
6. **创建超级用户**
```bash
python manage.py createsuperuser
```
7. **启动开发服务器**
```bash
python manage.py runserver
```
### 3. 访问系统
- 后台管理http://localhost:8000/admin
- 前端页面http://localhost:8000
## 使用说明
### 后台管理
1. 使用超级用户登录后台管理界面
2. 可以管理以下模块:
- 分支机构信息
- 联系人管理
- 活动记录
- 设备间图片
- 图纸管理
- 预算信息
- 公共屏幕
- 视频终端
### 前端功能
1. **分支机构列表**:查看所有分支机构信息
2. **分支机构详情**:查看分支机构的详细信息、联系人、活动等
3. **设备间图片**:查看分支机构设备间的图片
4. **公共屏幕**:查看公共屏幕信息
5. **视频终端**:查看视频终端信息
6. **统计信息**:查看系统统计数据
## 自定义命令
- `randomize_background_color`:为分支机构随机生成背景颜色
## 开发说明
### Tailwind CSS
项目使用Tailwind CSS进行样式开发
1. 配置文件:`tailwind.config.js`
2. 启动Tailwind开发服务器
```bash
./tailwindcss-windows-x64.exe -i ./huodong/static/huodong/input.css -o ./huodong/static/huodong/output.css --watch
```
### 数据模型
主要数据模型包括:
- Branch分支机构
- Contact联系人
- Activity活动
- EquipmentImage设备间图片
- Drawing图纸
- Budget预算
- PublicScreen公共屏幕
- VideoTerminal视频终端
## 许可证
[MIT License](LICENSE)
## 更新日志
### v1.0
- 初始版本
- 实现基本的分支机构和活动管理功能
### v1.1
- 增加联系人管理功能
- 增加设备间图片管理
### v1.2
- 增加图纸管理功能
- 增加预算管理功能
### v1.3
- 增加公共屏幕和视频终端管理
- 优化界面样式

Binary file not shown.

View File

@@ -0,0 +1,127 @@
from django.core.management.base import BaseCommand
from openai import OpenAI
from huodong.models import Branch
import time
import re
class Command(BaseCommand):
help = 'Update branch provinces using AI API'
def handle(self, *args, **options):
client = OpenAI(
base_url="https://integrate.api.nvidia.com/v1",
api_key="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"
)
def get_correct_province(branch_name, current_location, retry_count=0):
prompt = f"""请根据以下分支机构名称,返回一个正确的中国省份名称(全称,如:浙江省、北京市、上海市、广东省等)。
分支机构名称:{branch_name}
要求:
1. 只返回省份名称,不要包含任何其他文字
2. 省份名称必须是中国的省级行政区全称
3. 如果无法确定返回None
4. 不要被名称里含有的地名所影响
例子一:
分支机构名称:石家庄中山西路营业部
返回:河北省
例子二:
分支机构名称:北京市海淀区营业部
返回:北京市
例子三:
分支机构名称:奎屯北京东路营业部
返回:新疆维吾尔自治区
"""
try:
completion = client.chat.completions.create(
model="deepseek-ai/deepseek-r1",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
top_p=0.7,
max_tokens=100,
stream=False
)
message = completion.choices[0].message
reasoning = getattr(message, "reasoning_content", None)
content = message.content
if reasoning:
self.stdout.write(f" 推理过程: {reasoning[:200]}...")
if content is None:
if reasoning:
self.stdout.write(f" content为None尝试从推理过程提取...")
patterns = [
r'(?:正确的(?:答案)?应该是|所以|因此|答案是|结果为)[:\s]*(北京市|天津市|上海市|重庆市|河北省|山西省|辽宁省|吉林省|黑龙江省|江苏省|浙江省|安徽省|福建省|江西省|山东省|河南省|湖北省|湖南省|广东省|海南省|四川省|贵州省|云南省|陕西省|甘肃省|青海省|台湾省|内蒙古自治区|广西壮族自治区|西藏自治区|宁夏回族自治区|新疆维吾尔自治区|香港特别行政区|澳门特别行政区)',
r'(北京市|天津市|上海市|重庆市|河北省|山西省|辽宁省|吉林省|黑龙江省|江苏省|浙江省|安徽省|福建省|江西省|山东省|河南省|湖北省|湖南省|广东省|海南省|四川省|贵州省|云南省|陕西省|甘肃省|青海省|台湾省|内蒙古自治区|广西壮族自治区|西藏自治区|宁夏回族自治区|新疆维吾尔自治区|香港特别行政区|澳门特别行政区)'
]
for pattern in patterns:
match = re.search(pattern, reasoning)
if match:
result = match.group(1)
self.stdout.write(f" 从推理提取结果: {result}")
return result
if retry_count < 2:
self.stdout.write(f" content为None重试中 ({retry_count + 1}/2)...")
time.sleep(2)
return get_correct_province(branch_name, current_location, retry_count + 1)
else:
self.stdout.write(self.style.WARNING(f" 多次重试后仍无结果,使用当前省份"))
return current_location
result = content.strip()
self.stdout.write(f" 原始结果: {result}")
return result
except Exception as e:
if retry_count < 2:
self.stdout.write(self.style.WARNING(f" API调用失败: {e},重试中 ({retry_count + 1}/2)..."))
time.sleep(2)
return get_correct_province(branch_name, current_location, retry_count + 1)
else:
self.stdout.write(self.style.ERROR(f" 多次重试后仍失败: {e},使用当前省份"))
return current_location
branches = Branch.objects.all()
total = branches.count()
updated_count = 0
self.stdout.write(self.style.SUCCESS(f"开始处理 {total} 个分支机构..."))
self.stdout.write("=" * 80)
for index, branch in enumerate(branches, 1):
self.stdout.write(f"\n[{index}/{total}] 处理: {branch.name}")
self.stdout.write(f"当前省份: {branch.location}")
try:
correct_province = get_correct_province(branch.name, branch.location)
self.stdout.write(f"建议省份: {correct_province}")
if correct_province != branch.location:
old_location = branch.location
branch.location = correct_province
branch.save()
self.stdout.write(self.style.SUCCESS(f"✓ 已更新: {old_location} -> {correct_province}"))
updated_count += 1
else:
self.stdout.write("- 省份未变化")
except Exception as e:
self.stdout.write(self.style.ERROR(f"✗ 处理失败: {e}"))
self.stdout.write("-" * 80)
self.stdout.write(self.style.SUCCESS(f"\n处理完成!"))
self.stdout.write(f"总计: {total} 个分支机构")
self.stdout.write(f"已更新: {updated_count}")
self.stdout.write(f"未变化: {total - updated_count}")

View File

@@ -33,7 +33,8 @@ class Contact(models.Model):
('机房/设备间巡检人', '机房/设备间巡检人'),
('信息安全联系人', '信息安全联系人'),
('兼岗', '兼岗'),
('安全员', '安全员')
('安全员', '安全员'),
('合规管理人', '合规管理人')
# 可以添加更多类别
]
# 修改为支持多选的 CharField
@@ -64,6 +65,7 @@ class Activity(models.Model):
('搬迁', '搬迁'),
('原址装修', '原址装修'),
('撤销', '撤销'),
('变更', '变更'),
('其他技术问题', '其他技术问题')
), verbose_name='活动类型')

View File

@@ -1,3 +1,37 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.card-gradient {
@apply bg-gradient-to-br from-white via-blue-50/30 to-blue-100/50;
}
.card-gradient-early {
@apply bg-gradient-to-br from-emerald-50 via-white to-teal-100/50;
}
.card-gradient-latest {
@apply bg-gradient-to-br from-amber-50 via-white to-orange-100/50;
}
.table-header-gradient {
@apply bg-gradient-to-r from-blue-600 via-blue-700 to-indigo-700;
}
.hover-lift {
@apply transition-all duration-300 hover:-translate-y-1 hover:shadow-xl;
}
.timeline-dot {
@apply absolute top-1/2 w-5 h-5 rounded-full transform -translate-y-1/2 border-4 border-white shadow-lg;
}
.glass-effect {
@apply backdrop-blur-sm bg-white/80;
}
.section-divider {
@apply h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-blue-400 to-transparent;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +1,101 @@
<!DOCTYPE html>
{% load static %}
{% load custom_filters %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'huodong/output.css' %}">
<title>分支机构活动管理</title>
<script src="https://unpkg.com/alpinejs@3.14.1/dist/cdn.min.js" defer></script>
</head>
<body class="font-sans text-gray-900">
<!-- 顶部标题 -->
<header class="bg-gray-50 dark:bg-gray-800 dark:border-gray-700 p-4 text-center text-xl font-bold">
分支机构活动管理
<br>
2025年
</header>
<!-- 主体内容:左右两侧栏布局 -->
<div class="flex">
<!-- 左侧边栏 -->
<aside class="w-27 bg-gray-300 p-4 ">
{% block sidebar %}
<ul class="flex flex-col space-y-8 text-lg">
{% if request.resolver_match.view_name != 'branch-all' %}
<li>
<a href="{% url 'branch-all' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
返回首页
</a>
</li>
{% endif %}
<li>
<a href="{% url 'branchinfo' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
系统类型
</a>
</li>
<li>
<a href="{% url 'statistics' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
活动的统计数据
</a>
</li>
<li>
<a href="{% url 'contact-list' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
联系人信息
</a>
</li>
<li>
<a href="{% url 'equipment-images' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
设备间图片列表
</a>
</li>
<li>
<a href="{% url 'public-screens' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
公共电子屏列表
</a>
</li>
<li>
<a href="{% url 'video-terminals' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
视频设备终端
</a>
</li>
<li>
<a href="/admin/" class="bg-gradient-to-r from-gray-700 to-gray-900 hover:from-gray-800 hover:to-black text-white p-3 rounded-md font-medium shadow-lg transition-all duration-300 flex items-center justify-between border-l-4 border-amber-500" target="_blank">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3 text-amber-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z" />
</svg>
<span>后台管理</span>
</div>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
</li>
</ul>
{% endblock %}
</aside>
<!-- 主要内容区域 -->
<main class="flex-1 bg-white p-4">
{% block content %}
{% endblock %}
</main>
<!-- 右侧边栏 -->
<!-- 注意:如果需要右侧边栏,请取消下方注释,并相应调整主内容区域的宽度 -->
<!-- <aside class="w-1/3 bg-gray-300 p-4">
Right Sidebar
</aside> -->
</div>
<!-- 页脚 -->
<footer class="bg-gray-200 p-4 text-center mt-4">
© 2025 My Website. All rights reserved.
</footer>
</body>
<!DOCTYPE html>
{% load static %}
{% load custom_filters %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'huodong/output.css' %}">
<title>分支机构活动管理</title>
<script src="https://unpkg.com/alpinejs@3.14.1/dist/cdn.min.js" defer></script>
</head>
<body class="font-sans text-gray-900">
<!-- 顶部标题 -->
<header class="bg-gray-50 dark:bg-gray-800 dark:border-gray-700 p-4 text-center text-xl font-bold">
分支机构活动管理 {{ ''|get_current_year }}年
<br>
<small>统计周期:{{ ''|get_statistic_period }}</small>
</header>
<!-- 主体内容:左右两侧栏布局 -->
<div class="flex">
<!-- 左侧边栏 -->
<aside class="w-27 bg-gray-300 p-4 ">
{% block sidebar %}
<ul class="flex flex-col space-y-8 text-lg">
{% if request.resolver_match.view_name != 'branch-all' %}
<li>
<a href="{% url 'branch-all' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
返回首页
</a>
</li>
{% endif %}
<li>
<a href="{% url 'branchinfo' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
系统类型
</a>
</li>
<li>
<a href="{% url 'statistics' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
活动的统计数据
</a>
</li>
<li>
<a href="{% url 'contact-list' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
联系人信息
</a>
</li>
<li>
<a href="{% url 'equipment-images' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
设备间图片列表
</a>
</li>
<li>
<a href="{% url 'public-screens' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
公共电子屏列表
</a>
</li>
<li>
<a href="{% url 'video-terminals' %}" class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
视频设备终端
</a>
</li>
<li>
<a href="/admin/" class="bg-gradient-to-r from-gray-700 to-gray-900 hover:from-gray-800 hover:to-black text-white p-3 rounded-md font-medium shadow-lg transition-all duration-300 flex items-center justify-between border-l-4 border-amber-500" target="_blank">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3 text-amber-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z" />
</svg>
<span>后台管理</span>
</div>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
</li>
</ul>
{% endblock %}
</aside>
<!-- 主要内容区域 -->
<main class="flex-1 bg-white p-4">
{% block content %}
{% endblock %}
</main>
<!-- 右侧边栏 -->
<!-- 注意:如果需要右侧边栏,请取消下方注释,并相应调整主内容区域的宽度 -->
<!-- <aside class="w-1/3 bg-gray-300 p-4">
Right Sidebar
</aside> -->
</div>
<!-- 页脚 -->
<footer class="bg-gray-200 p-4 text-center mt-4">
© 2025 My Website. All rights reserved.
</footer>
</body>
</html>

View File

@@ -2,304 +2,478 @@
{% load custom_filters %}
{% block content %}
<div class="w-full max-w-4xl mx-auto p-6">
<div class="relative">
<!-- 时间线 -->
<div class="absolute top-1/2 left-0 right-0 h-0.5 bg-blue-500 transform -translate-y-1/2"></div>
<!-- 时间线区域 -->
<div class="w-full max-w-5xl mx-auto p-6">
<div class="relative py-8">
<!-- 时间线背景 -->
<div class="absolute top-1/2 left-0 right-0 h-1 bg-gradient-to-r from-emerald-400 via-blue-500 to-amber-400 rounded-full transform -translate-y-1/2 shadow-sm"></div>
<div class="relative z-10 flex justify-between items-center">
<!-- 第一个卡片 -->
<div class="w-[calc(50%-2rem)] bg-white rounded-lg shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl">
<div class="relative z-10 flex justify-between items-center gap-6">
<!-- 第一个卡片 - 最初活动 -->
<div class="w-[calc(50%-2.5rem)] group">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden transition-all duration-500 hover:shadow-2xl hover:-translate-y-2 border border-emerald-100">
<div class="h-1.5 bg-gradient-to-r from-emerald-400 to-teal-500"></div>
<div class="p-6 flex flex-col h-full">
<div class="flex-grow">
<div class="text-xs font-semibold text-blue-600 mb-2">最初活动</div>
<h3 class="text-xl font-bold mb-3 text-gray-800">{{ earliest_act.branch }}{{ earliest_act.name }}</h3>
<p class="text-sm text-gray-600 mb-4">
{{ earliest_act.description }}
</p>
</div>
<div class="flex justify-end">
<span class="inline-flex items-center rounded-full bg-blue-100 px-3 py-1 text-xs font-medium text-blue-800 whitespace-nowrap">
<svg xmlns="http://www.w3.org/2000/svg" class="mr-1.5 h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="flex items-center gap-2 mb-3">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-emerald-100 text-emerald-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ earliest_act.start_time|format_chinese_full_date }}
</span>
<span class="text-xs font-bold text-emerald-600 uppercase tracking-wider">最初活动</span>
</div>
</div>
</div>
<!-- 中间的箭头 -->
<div class="bg-white rounded-full p-3 shadow-md z-20">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
<!-- 第二个卡片 -->
<div class="w-[calc(50%-2rem)] bg-white rounded-lg shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl">
<div class="p-6 flex flex-col h-full">
<div class="flex-grow">
<div class="text-xs font-semibold text-blue-600 mb-2">最后活动</div>
<h3 class="text-xl font-bold mb-3 text-gray-800">{{ latest_act.branch }}{{ latest_act.name }}</h3>
<p class="text-sm text-gray-600 mb-4">
{{ latest_act.description }}
</p>
</div>
<div class="flex justify-end">
<span class="inline-flex items-center rounded-full bg-blue-100 px-3 py-1 text-xs font-medium text-blue-800 whitespace-nowrap">
<h3 class="text-lg font-bold mb-2 text-gray-800 group-hover:text-emerald-700 transition-colors">{{ earliest_act.branch }}{{ earliest_act.name }}</h3>
<p class="text-sm text-gray-600 mb-4 line-clamp-3">
{{ earliest_act.description }}
</p>
<div class="mt-auto pt-3 border-t border-emerald-100">
<span class="inline-flex items-center text-xs font-medium text-emerald-700 bg-emerald-50 px-3 py-1.5 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="mr-1.5 h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ latest_act.start_time|format_chinese_full_date }} </span>
{{ earliest_act.start_time|format_chinese_full_date }}
</span>
</div>
</div>
</div>
</div>
<!-- 装饰元素 -->
<div class="absolute top-1/2 left-0 w-4 h-4 bg-blue-500 rounded-full transform -translate-y-1/2 border-4 border-white"></div>
<div class="absolute top-1/2 right-0 w-4 h-4 bg-blue-500 rounded-full transform -translate-y-1/2 border-4 border-white"></div>
<!-- 中间的箭头 -->
<div class="flex-shrink-0 bg-white rounded-full p-4 shadow-lg z-20 border-2 border-blue-100 hover:scale-110 transition-transform duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
<!-- 第二个卡片 - 最后活动 -->
<div class="w-[calc(50%-2.5rem)] group">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden transition-all duration-500 hover:shadow-2xl hover:-translate-y-2 border border-amber-100">
<div class="h-1.5 bg-gradient-to-r from-amber-400 to-orange-500"></div>
<div class="p-6 flex flex-col h-full">
<div class="flex items-center gap-2 mb-3">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-100 text-amber-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<span class="text-xs font-bold text-amber-600 uppercase tracking-wider">最后活动</span>
</div>
<h3 class="text-lg font-bold mb-2 text-gray-800 group-hover:text-amber-700 transition-colors">{{ latest_act.branch }}{{ latest_act.name }}</h3>
<p class="text-sm text-gray-600 mb-4 line-clamp-3">
{{ latest_act.description }}
</p>
<div class="mt-auto pt-3 border-t border-amber-100">
<span class="inline-flex items-center text-xs font-medium text-amber-700 bg-amber-50 px-3 py-1.5 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="mr-1.5 h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ latest_act.start_time|format_chinese_full_date }}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- 装饰元素 -->
<div class="absolute top-1/2 left-0 w-6 h-6 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-full transform -translate-y-1/2 -translate-x-1/2 border-4 border-white shadow-lg"></div>
<div class="absolute top-1/2 right-0 w-6 h-6 bg-gradient-to-br from-amber-400 to-orange-500 rounded-full transform -translate-y-1/2 translate-x-1/2 border-4 border-white shadow-lg"></div>
</div>
</div>
<table class="items-center bg-transparent w-full border-collapse ">
<thead>
<tr class="bg-gray-100">
<th class="px-6 bg-blueGray-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-base uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-left">
分支机构名称
<br>
新建-搬迁-装修
</th>
<th class="px-6 bg-blueGray-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-base uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-right">
活动的总数量
</th>
<th class="px-6 bg-blueGray-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-base uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-right">
活动进行中的数量
</th>
</tr>
</thead>
<tbody>
{% for item in branches %}
<!-- 这里是其他行的处理逻辑 -->
<tr class="{% if forloop.counter|divisibleby:2 %}bg-gray-100{% else %}bg-white{% endif %}">
<td class="px-6 py-4 whitespace-no-wrap text-left border-b border-gray-200">
<a href="{% url 'branch-detail' item.branch.pk %}"
class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
{{ item.branch.name }}
</a>
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-right">
{{ item.total_count }}
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-right">
{{ item.onging_count }}
</td>
</tr>
{% endfor %}
<!-- 这里是最后一行的处理逻辑 -->
<tr>
<td class="px-6 py-4 whitespace-no-wrap text-left border-b border-gray-200 font-bold text-center">
总计: {{ total_branch_count }}
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-right">
{{ total_activities }}
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-right">
{{ ongoing_activities_count }}
</td>
</tr>
</tbody>
</table>
<hr class="h-px my-8 bg-red-500 border-1 dark:bg-red-700">
<!--插入事件-->
<div class="p-5 border border-gray-100 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700">
<time class="text-lg font-semibold text-gray-900 dark:text-white">事件</time>
<ol class="mt-3 divide-y divider-gray-200 dark:divide-gray-700">
{% for event in ongoing_events %}
<li>
<div href="#" class="items-center block p-3 sm:flex hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="text-gray-600 dark:text-gray-400">
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"/>
</svg>
开始时间 {{ event.start_time| date:"Y年m月d日" }}
</span>
<div class="text-base font-normal">
<span class="font-medium text-gray-900 dark:text-white">
{% if not event.end_time %}
<span class="text-lg font-semibold text-red-900 dark:text-white">
{{ event.name }}-未完成
<!-- 统计表格 -->
<div class="w-full max-w-5xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-100">
<div class="px-6 py-4 bg-gradient-to-r from-blue-600 via-blue-700 to-indigo-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
分支机构活动统计
</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-slate-50 to-gray-100 border-b-2 border-blue-200">
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构名称
<span class="text-xs font-normal text-gray-500 normal-case">(新建-搬迁-装修)</span>
</div>
</th>
<th class="px-6 py-4 text-right text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center justify-end gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
活动总数
</div>
</th>
<th class="px-6 py-4 text-right text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center justify-end gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
进行中
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for item in branches %}
<tr class="{% if forloop.counter|divisibleby:2 %}bg-slate-50/50{% else %}bg-white{% endif %} hover:bg-blue-50 transition-colors duration-200 group">
<td class="px-6 py-4 whitespace-nowrap">
<a href="{% url 'branch-detail' item.branch.pk %}"
class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium text-gray-700 bg-gray-100 hover:bg-blue-600 hover:text-white transition-all duration-200 group-hover:shadow-md">
{{ item.branch.name }}
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
<span class="inline-flex items-center justify-center min-w-[2rem] px-2.5 py-1 rounded-full text-sm font-semibold {% if item.total_count > 0 %}bg-blue-100 text-blue-700{% else %}bg-gray-100 text-gray-500{% endif %}">
{{ item.total_count }}
</span>
{% else %}
<span class="text-lg font-semibold text-blue-900 dark:text-white">
{{ event.name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
<span class="inline-flex items-center justify-center min-w-[2rem] px-2.5 py-1 rounded-full text-sm font-semibold {% if item.onging_count > 0 %}bg-green-100 text-green-700{% else %}bg-gray-100 text-gray-500{% endif %}">
{{ item.onging_count }}
</span>
</td>
</tr>
{% endfor %}
<!-- 总计行 -->
<tr class="bg-gradient-to-r from-blue-50 to-indigo-50 border-t-2 border-blue-200">
<td class="px-6 py-4 whitespace-nowrap font-bold text-gray-800">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
总计 ({{ total_branch_count }} 个分支机构)
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
<span class="inline-flex items-center justify-center min-w-[2.5rem] px-3 py-1.5 rounded-full text-base font-bold bg-blue-600 text-white shadow-md">
{{ total_activities }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
<span class="inline-flex items-center justify-center min-w-[2.5rem] px-3 py-1.5 rounded-full text-base font-bold {% if ongoing_activities_count > 0 %}bg-green-500 text-white{% else %}bg-gray-400 text-white{% endif %} shadow-md">
{{ ongoing_activities_count }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-purple-400 to-transparent"></div>
<!-- 事件列表 -->
<div class="w-full max-w-5xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-purple-100">
<div class="px-6 py-4 bg-gradient-to-r from-purple-600 via-purple-700 to-indigo-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
事件列表
<span class="ml-auto text-sm font-normal bg-white/20 px-3 py-1 rounded-full">{{ ongoing_events|length }} 个事件</span>
</h2>
</div>
<div class="divide-y divide-gray-100">
{% for event in ongoing_events %}
<div class="p-5 hover:bg-purple-50/50 transition-colors duration-200 group">
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
<!-- 时间轴标记 -->
<div class="flex-shrink-0 flex flex-col items-center">
<div class="w-10 h-10 rounded-full {% if not event.end_time %}bg-gradient-to-br from-red-400 to-red-600{% else %}bg-gradient-to-br from-purple-400 to-purple-600{% endif %} flex items-center justify-center text-white shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
</div>
<!-- 内容 -->
<div class="flex-grow min-w-0">
<div class="flex flex-wrap items-center gap-2 mb-2">
<h3 class="text-lg font-bold {% if not event.end_time %}text-red-700{% else %}text-gray-800{% endif %} group-hover:text-purple-700 transition-colors">
{{ event.name }}{% if not event.end_time %}<span class="ml-2 text-sm font-medium text-red-500 bg-red-100 px-2 py-0.5 rounded-full">进行中</span>{% endif %}
</h3>
</div>
<p class="text-sm text-gray-600 mb-3 line-clamp-2">{{ event.description }}</p>
<div class="flex flex-wrap items-center gap-4 text-xs text-gray-500">
<span class="inline-flex items-center gap-1 bg-blue-50 text-blue-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ event.start_time|date:"Y年m月d日" }}
</span>
{% if event.end_time %}
<span class="inline-flex items-center gap-1 bg-green-50 text-green-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ event.end_time|date:"Y年m月d日" }}
</span>
{% endif %}
</span>
</div>
<div class="text-sm font-normal">
{{ event.description }}
</div>
<div class="text-sm font-normal">
{% with branch_count=event.branches.count %}
{% if branch_count == all_branch_count %}
涉及分支机构:全辖分支机构
{% elif branch_count == 1 %}
涉及分支机构:{{ event.branches.first.name }}
{% elif branch_count > 1 %}
涉及分支机构:{{ event.branches.all.0.name }}、{{ event.branches.all.1.name }}等{{ branch_count }}个营业部
{% else %}
涉及分支机构:暂无
{% endif %}
{% endwith %}
</div>
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"/>
</svg>
结束时间 {{ event.end_time| date:"Y年m月d日" }}
</span>
</div>
</div>
</li>
{% endfor %}
</ol>
</div>
<hr class="h-px my-8 bg-red-500 border-1 dark:bg-red-700">
<div class="p-5 border border-gray-100 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700">
<time class="text-lg font-semibold text-gray-900 dark:text-white">活动中的</time>
<ol class="mt-3 divide-y divider-gray-200 dark:divide-gray-700">
{% for huodong in ongoing_activities %}
<li>
<div href="#" class="items-center block p-3 sm:flex hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="text-gray-600 dark:text-gray-400">
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"/>
</svg>
开始时间 {{ huodong.start_time| date:"Y年m月d日" }}
</span>
<div class="text-base font-normal">
<span class="font-medium text-gray-900 dark:text-white">
{% if not huodong.end_time %}
<span class="text-lg font-semibold text-red-900 dark:text-white">
<a href="{% url 'branch-detail' huodong.branch.pk %}"
class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
{{ huodong.branch }}
</a>
{{ huodong.name }}-未完成
</span>
{% endif %}
</span>
</div>
<div class="text-sm font-normal">
{{ huodong.description }}
</div>
<div class="mt-3 text-sm text-gray-600">
{% with branch_count=event.branches.count %}
{% if branch_count == all_branch_count %}
<span class="inline-flex items-center gap-1 text-purple-700 bg-purple-50 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
涉及:全辖分支机构
</span>
{% elif branch_count == 1 %}
<span class="inline-flex items-center gap-1 text-blue-700 bg-blue-50 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
涉及:{{ event.branches.first.name }}
</span>
{% elif branch_count > 1 %}
<span class="inline-flex items-center gap-1 text-blue-700 bg-blue-50 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
涉及:{{ event.branches.all.0.name }}、{{ event.branches.all.1.name }}等{{ branch_count }}个营业部
</span>
{% else %}
<span class="inline-flex items-center gap-1 text-gray-500 bg-gray-100 px-2.5 py-1 rounded-full">
涉及:暂无
</span>
{% endif %}
{% endwith %}
</div>
</div>
</div>
</div>
</li>
{% endfor %}
</ol>
{% empty %}
<div class="p-8 text-center text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
暂无事件
</div>
{% endfor %}
</div>
</div>
</div>
<hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-orange-400 to-transparent"></div>
<!-- 活动中的列表 -->
<div class="w-full max-w-5xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-orange-100">
<div class="px-6 py-4 bg-gradient-to-r from-orange-500 via-orange-600 to-red-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
进行中的活动
<span class="ml-auto text-sm font-normal bg-white/20 px-3 py-1 rounded-full">{{ ongoing_activities|length }} 个活动</span>
</h2>
</div>
<div class="divide-y divide-gray-100">
{% for huodong in ongoing_activities %}
<div class="p-5 hover:bg-orange-50/50 transition-colors duration-200 group">
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
<!-- 状态标记 -->
<div class="flex-shrink-0">
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-red-400 to-red-600 flex items-center justify-center text-white shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<!-- 内容 -->
<div class="flex-grow min-w-0">
<div class="flex flex-wrap items-center gap-2 mb-2">
<a href="{% url 'branch-detail' huodong.branch.pk %}"
class="inline-flex items-center px-2.5 py-1 rounded-lg text-sm font-medium bg-orange-100 text-orange-700 hover:bg-orange-600 hover:text-white transition-colors">
{{ huodong.branch }}
</a>
<h3 class="text-lg font-bold text-red-700 group-hover:text-red-800 transition-colors">
{{ huodong.name }}
</h3>
<span class="text-xs font-medium text-white bg-red-500 px-2 py-0.5 rounded-full">未完成</span>
</div>
<p class="text-sm text-gray-600 mb-3 line-clamp-2">{{ huodong.description }}</p>
<div class="flex flex-wrap items-center gap-4 text-xs text-gray-500">
<span class="inline-flex items-center gap-1 bg-blue-50 text-blue-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
开始时间:{{ huodong.start_time|date:"Y年m月d日" }}
</span>
</div>
</div>
</div>
</div>
{% empty %}
<div class="p-8 text-center text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
暂无进行中的活动
</div>
{% endfor %}
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-teal-400 to-transparent"></div>
<!-- 今年活动分类展示 -->
{% for scope, scope_data in grouped_activities.items %}
<div class="p-5 border border-gray-100 rounded-lg dark:bg-gray-800 dark:border-gray-700">
<time class="text-lg font-semibold text-gray-900 dark:text-white">今年的{{ scope }}(共{{ scope_data.branch_count }}个分支机构)</time>
<ol class="mt-3 divide-y divider-gray-200 dark:divide-gray-700">
{% for activity in scope_data.activities %}
<li style= "background-color: {{ activity.branch.background_color }};" >
<div class="items-center block p-3 sm:flex hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="text-gray-600 dark:text-gray-400">
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor"
class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"/>
</svg>
开始时间 {{ activity.start_time| date:"Y年m月d日" }}
</span>
<div class="text-base font-normal">
<span class="font-medium text-gray-900 dark:text-white">
<div class="w-full max-w-5xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-teal-100">
<div class="px-6 py-4 bg-gradient-to-r from-teal-500 via-teal-600 to-cyan-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
今年的{{ scope }}
<span class="ml-auto text-sm font-normal bg-white/20 px-3 py-1 rounded-full">{{ scope_data.branch_count }} 个分支机构</span>
</h2>
</div>
<div class="divide-y divide-gray-100">
{% for activity in scope_data.activities %}
<div class="p-5 hover:bg-teal-50/50 transition-colors duration-200 group" style="background-color: {{ activity.branch.background_color }};">
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
<!-- 状态标记 -->
<div class="flex-shrink-0">
<div class="w-10 h-10 rounded-full {% if not activity.end_time %}bg-gradient-to-br from-red-400 to-red-600{% else %}bg-gradient-to-br from-teal-400 to-teal-600{% endif %} flex items-center justify-center text-white shadow-md">
{% if not activity.end_time %}
<span class="text-lg font-semibold text-red-900 dark:text-white">
<a href="{% url 'branch-detail' activity.branch.pk %}"
class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
{{ activity.branch }}
</a>
{{ activity.name }}-未完成
</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{% else %}
<span class="text-lg font-semibold text-gray-900 dark:text-white">
<a href="{% url 'branch-detail' activity.branch.pk %}"
class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
{{ activity.branch }}
</a>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{% endif %}
</div>
</div>
<!-- 内容 -->
<div class="flex-grow min-w-0">
<div class="flex flex-wrap items-center gap-2 mb-2">
<a href="{% url 'branch-detail' activity.branch.pk %}"
class="inline-flex items-center px-2.5 py-1 rounded-lg text-sm font-medium bg-teal-100 text-teal-700 hover:bg-teal-600 hover:text-white transition-colors">
{{ activity.branch }}
</a>
<h3 class="text-lg font-bold {% if not activity.end_time %}text-red-700{% else %}text-gray-800{% endif %} group-hover:text-teal-700 transition-colors">
{{ activity.name }}
</h3>
{% if not activity.end_time %}
<span class="text-xs font-medium text-white bg-red-500 px-2 py-0.5 rounded-full">未完成</span>
{% endif %}
</div>
<p class="text-sm text-gray-600 mb-3 line-clamp-2">{{ activity.description }}</p>
<div class="flex flex-wrap items-center gap-4 text-xs text-gray-500">
<span class="inline-flex items-center gap-1 bg-blue-50 text-blue-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ activity.start_time|date:"Y年m月d日" }}
</span>
{% if activity.end_time %}
<span class="inline-flex items-center gap-1 bg-green-50 text-green-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ activity.end_time|date:"Y年m月d日" }}
</span>
{% endif %}
</span>
</div>
</div>
<div class="text-sm font-normal">
{{ activity.description }}
</div>
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
fill="currentColor" viewBox="0 0 20 20">
<path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
</svg>
结束时间 {{ activity.end_time| date:"Y年m月d日" }}
</span>
</div>
</div>
</li>
{% endfor %}
</ol>
{% empty %}
<div class="p-8 text-center text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
暂无活动
</div>
{% endfor %}
</div>
</div>
</div>
<hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-gray-300 to-transparent"></div>
{% endfor %}
<time class="text-lg font-semibold text-gray-900 dark:text-white">
今年的
{% for scope, scope_data in grouped_activities.items %}
{{ scope }}{{ scope_data.branch_count }}个{% if forloop.last %}。{% else %}{% endif %}
{% endfor %}
</time>
<hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
<!-- 今年统计汇总 -->
<div class="w-full max-w-5xl mx-auto p-6">
<div class="bg-gradient-to-r from-teal-50 to-cyan-50 rounded-xl p-6 border border-teal-200">
<h3 class="text-lg font-bold text-teal-800 mb-3 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
今年统计
</h3>
<div class="flex flex-wrap gap-3">
{% for scope, scope_data in grouped_activities.items %}
<span class="inline-flex items-center px-4 py-2 rounded-full bg-white shadow-sm border border-teal-100">
<span class="w-2 h-2 rounded-full bg-teal-500 mr-2"></span>
<span class="font-medium text-gray-700">{{ scope }}</span>
<span class="ml-2 text-teal-600 font-bold">{{ scope_data.branch_count }}个</span>
</span>
{% endfor %}
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-indigo-400 to-transparent"></div>
<!-- 历年活动统计 -->
{% for year in historical_years %}
{% with year_data=historical_grouped_activities|get_item:year %}
{% if year_data %}
<time class="text-lg font-semibold text-gray-900 dark:text-white">
{{ year }}年的
{% for scope, scope_data in year_data.items %}
{{ scope }}{{ scope_data.branch_count }}个{% if forloop.last %}。{% else %}{% endif %}
{% endfor %}
</time>
<hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
{% endif %}
{% endwith %}
{% endfor %}
<div class="w-full max-w-5xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-indigo-100">
<div class="px-6 py-4 bg-gradient-to-r from-indigo-500 via-indigo-600 to-purple-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
历年活动统计
</h2>
</div>
<div class="p-6 space-y-4">
{% for year in historical_years %}
{% with year_data=historical_grouped_activities|get_item:year %}
{% if year_data %}
<div class="bg-gradient-to-r from-indigo-50 to-purple-50 rounded-xl p-5 border border-indigo-100">
<h3 class="text-lg font-bold text-indigo-800 mb-3 flex items-center gap-2">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-600 text-white text-sm font-bold">{{ year }}</span>
<span>年统计</span>
</h3>
<div class="flex flex-wrap gap-3">
{% for scope, scope_data in year_data.items %}
<span class="inline-flex items-center px-4 py-2 rounded-full bg-white shadow-sm border border-indigo-100">
<span class="w-2 h-2 rounded-full bg-indigo-500 mr-2"></span>
<span class="font-medium text-gray-700">{{ scope }}</span>
<span class="ml-2 text-indigo-600 font-bold">{{ scope_data.branch_count }}个</span>
</span>
{% endfor %}
</div>
</div>
{% endif %}
{% endwith %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,51 +2,73 @@
{% block content %}
<!-- Component starts here -->
<h2 class="flex flex-row flex-nowrap items-center my-8">
<span class="flex-grow block border-t border-black" aria-hidden="true" role="presentation"></span>
<span class="flex-none block mx-4 px-4 py-2.5 text-base leading-none font-medium uppercase bg-black text-white">
{{ branch.name }}
</span>
<span class="flex-grow block border-t border-black" aria-hidden="true" role="presentation"></span>
</h2>
<!-- Component ends here -->
<!-- 页面标题 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-blue-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-blue-600 to-indigo-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
{{ branch.name }}
</h1>
</div>
</div>
</div>
<!-- 投入预算表 -->
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
<h2 class="text-2xl font-bold mb-4">投入预算表</h2>
<p class="text-gray-700 mb-4">分支机构项目的预算表,包括设备和基础设施明细</p>
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-blue-100">
<div class="px-6 py-4 bg-gradient-to-r from-blue-600 via-blue-700 to-indigo-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
投入预算表
</h2>
</div>
<div class="p-6">
<p class="text-gray-600 mb-6">分支机构项目的预算表,包括设备和基础设施明细</p>
<!-- 预算模板导入功能 -->
{% if budget_templates %} <!-- 只有当有模板时才显示导入功能 -->
<div class="mb-6 p-4 bg-blue-50 rounded-lg">
<h3 class="text-lg font-semibold mb-2">预算模板导入</h3>
<p class="text-sm text-gray-600 mb-3">从模板一键导入预算,快速生成预算表</p>
<form method="POST" action="{% url 'import-budget-template' branch.id %}">
{% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="template" class="block text-sm font-medium text-gray-700 mb-1">选择模板</label>
<select id="template" name="template" class="w-full p-2 border border-gray-300 rounded-md">
{% for template in budget_templates %}
<option value="{{ template.id }}">{{ template.name }}{% if template.is_default %} (默认){% endif %}</option>
{% endfor %}
</select>
</div>
<div>
<label for="budget_name" class="block text-sm font-medium text-gray-700 mb-1">预算名称</label>
<input type="text" id="budget_name" name="budget_name" placeholder="请输入预算名称" class="w-full p-2 border border-gray-300 rounded-md">
</div>
<div class="flex items-end">
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md">
一键导入
</button>
</div>
<!-- 预算模板导入功能 -->
{% if budget_templates %}
<div class="mb-6 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-200">
<h3 class="text-lg font-semibold mb-2 text-blue-800 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
预算模板导入
</h3>
<p class="text-sm text-gray-600 mb-4">从模板一键导入预算,快速生成预算表</p>
<form method="POST" action="{% url 'import-budget-template' branch.id %}">
{% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="template" class="block text-sm font-medium text-gray-700 mb-1">选择模板</label>
<select id="template" name="template" class="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all">
{% for template in budget_templates %}
<option value="{{ template.id }}">{{ template.name }}{% if template.is_default %} (默认){% endif %}</option>
{% endfor %}
</select>
</div>
<div>
<label for="budget_name" class="block text-sm font-medium text-gray-700 mb-1">预算名称</label>
<input type="text" id="budget_name" name="budget_name" placeholder="请输入预算名称" class="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all">
</div>
<div class="flex items-end">
<button type="submit" class="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white px-6 py-2.5 rounded-lg font-medium shadow-md hover:shadow-lg transition-all duration-200 flex items-center justify-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
一键导入
</button>
</div>
</div>
</form>
</div>
</form>
</div>
{% endif %}
{% endif %}
{% if budgets %}
{% for budget in budgets %}
@@ -54,215 +76,438 @@
<h3 class="text-lg font-semibold mb-3">{{ budget.name }} - 总预算: ¥{{ budget.total_budget }}</h3>
<!-- 设备预算部分 -->
<div class="mb-4">
<h4 class="text-md font-semibold mb-2">设备预算明细</h4>
<div class="mb-6">
<h4 class="text-md font-bold mb-3 text-gray-800 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
设备预算明细
</h4>
{% if budget.equipment_budgets.all %}
<div class="overflow-x-auto">
<div class="overflow-x-auto rounded-xl border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<thead class="bg-gradient-to-r from-slate-50 to-gray-100">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">项目</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">型号</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">单价</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">采购方式</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">数量</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">小计</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">项目</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">型号</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">单价</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">采购方式</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">数量</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">小计</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-white divide-y divide-gray-100">
{% for equipment in budget.equipment_budgets.all %}
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.project }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.model }}</td>
<td class="px-6 py-4 whitespace-nowrap">¥{{ equipment.unit_price }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.get_procurement_method_display }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.quantity }}</td>
<td class="px-6 py-4 whitespace-nowrap">¥{{ equipment.subtotal }}</td>
<tr class="hover:bg-blue-50/50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">{{ equipment.project }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ equipment.model }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">¥{{ equipment.unit_price }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ equipment.get_procurement_method_display }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">{{ equipment.quantity }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-green-600">¥{{ equipment.subtotal }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-500 italic">暂无设备预算明细</p>
<div class="p-6 text-center bg-gray-50 rounded-xl border border-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
<p class="text-gray-500">暂无设备预算明细</p>
</div>
{% endif %}
</div>
<!-- 基础设施预算部分 -->
<div>
<h4 class="text-md font-semibold mb-2">基础设施预算明细</h4>
<h4 class="text-md font-bold mb-3 text-gray-800 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
基础设施预算明细
</h4>
{% if budget.infrastructure_budgets.all %}
<div class="overflow-x-auto">
<div class="overflow-x-auto rounded-xl border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<thead class="bg-gradient-to-r from-slate-50 to-gray-100">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">名称</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">备注</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">单价</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">单位</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">数量</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">小计</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">说明</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">名称</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">备注</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">单价</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">单位</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">数量</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">小计</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">说明</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-white divide-y divide-gray-100">
{% for infrastructure in budget.infrastructure_budgets.all %}
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.name }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.remarks }}</td>
<td class="px-6 py-4 whitespace-nowrap">¥{{ infrastructure.unit_price }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.unit }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.quantity }}</td>
<td class="px-6 py-4 whitespace-nowrap">¥{{ infrastructure.subtotal }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.description }}</td>
<tr class="hover:bg-teal-50/50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">{{ infrastructure.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ infrastructure.remarks }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">¥{{ infrastructure.unit_price }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ infrastructure.unit }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">{{ infrastructure.quantity }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-green-600">¥{{ infrastructure.subtotal }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ infrastructure.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-500 italic">暂无基础设施预算明细</p>
<div class="p-6 text-center bg-gray-50 rounded-xl border border-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<p class="text-gray-500">暂无基础设施预算明细</p>
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% else %}
<p class="text-gray-500 italic">暂无预算信息</p>
<div class="p-8 text-center bg-gray-50 rounded-xl border border-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto mb-4 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
<p class="text-gray-500 text-lg">暂无预算信息</p>
</div>
{% endif %}
</div>
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
<h2 class="text-2xl font-bold mb-4">基本信息</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p class="text-gray-700"><strong>信息系统分类:</strong> {{ branch.category|default:"暂无" }}</p>
<p class="text-gray-700"><strong>联系人:</strong> {{ branch.contact_person|default:"暂无" }}</p>
<p class="text-gray-700"><strong>联系电话:</strong> {{ branch.contact_phone|default:"暂无" }}</p>
</div>
<div>
<p class="text-gray-700"><strong>地址:</strong> {{ branch.address|default:"暂无" }}</p>
<p class="text-gray-700"><strong>成立时间:</strong> {{ branch.established_date|date:"Y-m-d"|default:"暂无" }}</p>
</div>
</div>
</div>
<!-- 事件信息 -->
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
<h2 class="text-2xl font-bold mb-4">关联事件</h2>
{% if events %}
<div class="space-y-3">
{% for event in events %}
<div class="border-l-4 border-blue-500 pl-4 py-2 bg-blue-50 rounded">
<h3 class="font-semibold text-lg">{{ event.name }}</h3>
<p class="text-sm text-gray-600">
{% if event.end_time %}
开始日期{{ event.start_time|date:"Y年m月d日" }} 结束日期{{ event.end_time|date:"Y年m月d日" }}
{% else %}
开始日期{{ event.start_time|date:"Y年m月d日" }},现在进行中
{% endif %}
</p>
<p class="mt-1 text-gray-700">{{ event.description|truncatechars:100 }}</p>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-cyan-400 to-transparent"></div>
<!-- 基本信息 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-cyan-100">
<div class="px-6 py-4 bg-gradient-to-r from-cyan-500 via-cyan-600 to-teal-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
基本信息
</h2>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-gray-500 italic">暂无事件记录</p>
{% endif %}
</div>
<!-- 设备间图片 -->
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
<h2 class="text-2xl font-bold mb-4">设备间图片</h2>
{% if equipment_images %}
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{% for image in equipment_images %}
<div class="overflow-hidden rounded-lg shadow">
<img src="{{ image.image.url }}" alt="设备间图片" class="w-full h-48 object-cover">
<div class="p-2 bg-gray-50">
<p class="text-sm text-gray-700">{{ image.description|default:"无描述" }}</p>
<p class="text-xs text-gray-500 mt-1">{{ image.upload_date|date:"Y-m-d" }}</p>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-gray-500 italic">暂无设备间图片</p>
{% endif %}
</div>
<!-- 公共电子屏 -->
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
<h2 class="text-2xl font-bold mb-4">公共电子屏</h2>
{% if public_screens %}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">描述</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后演练</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">更新时间</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for screen in public_screens %}
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ screen.get_screen_type_display }}</td>
<td class="px-6 py-4">{{ screen.description|default:"无" }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ screen.last_drill.date|date:"Y-m-d"|default:"未演练" }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ screen.updated_at|date:"Y-m-d H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-500 italic">暂无公共电子屏信息</p>
{% endif %}
</div>
{% for scope, activities in grouped_activities.items %}
<div class="p-5 border border-gray-100 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700">
<time class="text-lg font-semibold text-gray-900 dark:text-white">{{ scope }}</time>
<ol class="mt-3 divide-y divider-gray-200 dark:divide-gray-700">
{% for huodong in activities %}
<li style="background-color: {{ huodong.branch.background_color }};">
<div class="items-center block p-3 sm:flex hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="text-gray-600 dark:text-gray-400">
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9" />
</svg>
开始时间 {{ huodong.start_time| date:"Y年m月d日" }}
</span>
<div class="text-base font-normal">
<span class="font-medium text-gray-900 dark:text-white">
{% if not huodong.end_time %}
<span class="text-lg font-semibold text-red-900 dark:text-white">
{{ huodong.name }}-未完成
</span>
{% else %}
<span class="text-lg font-semibold text-gray-900 dark:text-white">
{{ huodong.name }}
</span>
{% endif %}
</span>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4">
<div class="flex items-start gap-3 p-4 bg-gray-50 rounded-xl">
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">信息系统分类</p>
<p class="text-base font-medium text-gray-800">{{ branch.category|default:"暂无" }}</p>
</div>
</div>
<div class="text-sm font-normal">
{{ huodong.description }}
<div class="flex items-start gap-3 p-4 bg-gray-50 rounded-xl">
<div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">联系人</p>
<p class="text-base font-medium text-gray-800">{{ branch.contact_person|default:"暂无" }}</p>
</div>
</div>
<div class="flex items-start gap-3 p-4 bg-gray-50 rounded-xl">
<div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">联系电话</p>
<p class="text-base font-medium text-gray-800">{{ branch.contact_phone|default:"暂无" }}</p>
</div>
</div>
</div>
<div class="space-y-4">
<div class="flex items-start gap-3 p-4 bg-gray-50 rounded-xl">
<div class="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">地址</p>
<p class="text-base font-medium text-gray-800">{{ branch.address|default:"暂无" }}</p>
</div>
</div>
<div class="flex items-start gap-3 p-4 bg-gray-50 rounded-xl">
<div class="w-10 h-10 rounded-full bg-pink-100 flex items-center justify-center flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-pink-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">成立时间</p>
<p class="text-base font-medium text-gray-800">{{ branch.established_date|date:"Y年m月d日"|default:"暂无" }}</p>
</div>
</div>
<span class="inline-flex items-center text-xs font-normal text-gray-400 dark:text-gray-400">
<svg class="w-2.5 h-2.5 text-blue-800 dark:text-blue-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
</svg>
结束时间 {{ huodong.end_time| date:"Y年m月d日" }}
</span>
</div>
</div>
</li>
{% endfor %}
</ol>
</div>
</div>
</div>
<hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-purple-400 to-transparent"></div>
<!-- 事件信息 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-purple-100">
<div class="px-6 py-4 bg-gradient-to-r from-purple-600 via-purple-700 to-indigo-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
关联事件
</h2>
</div>
<div class="p-6">
{% if events %}
<div class="space-y-4">
{% for event in events %}
<div class="p-5 bg-gradient-to-r from-purple-50 to-indigo-50 rounded-xl border-l-4 border-purple-500 hover:shadow-md transition-shadow">
<h3 class="font-bold text-lg text-purple-800 mb-2">{{ event.name }}</h3>
<div class="flex flex-wrap items-center gap-3 mb-3">
<span class="inline-flex items-center gap-1 text-xs bg-white px-2.5 py-1 rounded-full text-purple-700 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ event.start_time|date:"Y年m月d日" }}
</span>
{% if event.end_time %}
<span class="inline-flex items-center gap-1 text-xs bg-green-100 px-2.5 py-1 rounded-full text-green-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ event.end_time|date:"Y年m月d日" }}
</span>
{% else %}
<span class="inline-flex items-center gap-1 text-xs bg-red-100 px-2.5 py-1 rounded-full text-red-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
进行中
</span>
{% endif %}
</div>
<p class="text-gray-700">{{ event.description|truncatechars:100 }}</p>
</div>
{% endfor %}
</div>
{% else %}
<div class="p-8 text-center bg-gray-50 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto mb-4 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p class="text-gray-500 text-lg">暂无事件记录</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-emerald-400 to-transparent"></div>
<!-- 设备间图片 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-emerald-100">
<div class="px-6 py-4 bg-gradient-to-r from-emerald-500 via-emerald-600 to-teal-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
设备间图片
</h2>
</div>
<div class="p-6">
{% if equipment_images %}
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{% for image in equipment_images %}
<div class="overflow-hidden rounded-xl shadow-lg hover:shadow-xl transition-shadow group">
<div class="relative overflow-hidden">
<img src="{{ image.image.url }}" alt="设备间图片" class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300">
</div>
<div class="p-4 bg-gray-50">
<p class="text-sm font-medium text-gray-800">{{ image.description|default:"无描述" }}</p>
<p class="text-xs text-gray-500 mt-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ image.upload_date|date:"Y年m月d日" }}
</p>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="p-8 text-center bg-gray-50 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto mb-4 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p class="text-gray-500 text-lg">暂无设备间图片</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-amber-400 to-transparent"></div>
<!-- 公共电子屏 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-amber-100">
<div class="px-6 py-4 bg-gradient-to-r from-amber-500 via-amber-600 to-orange-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
公共电子屏
</h2>
</div>
<div class="p-6">
{% if public_screens %}
<div class="overflow-x-auto rounded-xl border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gradient-to-r from-slate-50 to-gray-100">
<tr>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">类型</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">描述</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">最后演练</th>
<th class="px-6 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">更新时间</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
{% for screen in public_screens %}
<tr class="hover:bg-amber-50/50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-800">
{{ screen.get_screen_type_display }}
</span>
</td>
<td class="px-6 py-4 text-sm text-gray-700">{{ screen.description|default:"无" }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
{% if screen.last_drill %}
<span class="inline-flex items-center gap-1 text-green-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ screen.last_drill.date|date:"Y-m-d" }}
</span>
{% else %}
<span class="text-gray-400">未演练</span>
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ screen.updated_at|date:"Y-m-d H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="p-8 text-center bg-gray-50 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto mb-4 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<p class="text-gray-500 text-lg">暂无公共电子屏信息</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-teal-400 to-transparent"></div>
<!-- 活动列表 -->
{% for scope, activities in grouped_activities.items %}
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-teal-100">
<div class="px-6 py-4 bg-gradient-to-r from-teal-500 via-teal-600 to-cyan-600">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
{{ scope }}
</h2>
</div>
<div class="divide-y divide-gray-100">
{% for huodong in activities %}
<div class="p-5 hover:bg-teal-50/50 transition-colors duration-200" style="background-color: {{ huodong.branch.background_color }};">
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
<!-- 状态标记 -->
<div class="flex-shrink-0">
<div class="w-10 h-10 rounded-full {% if not huodong.end_time %}bg-gradient-to-br from-red-400 to-red-600{% else %}bg-gradient-to-br from-teal-400 to-teal-600{% endif %} flex items-center justify-center text-white shadow-md">
{% if not huodong.end_time %}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{% endif %}
</div>
</div>
<!-- 内容 -->
<div class="flex-grow min-w-0">
<div class="flex flex-wrap items-center gap-2 mb-2">
<h3 class="text-lg font-bold {% if not huodong.end_time %}text-red-700{% else %}text-gray-800{% endif %}">
{{ huodong.name }}
</h3>
{% if not huodong.end_time %}
<span class="text-xs font-medium text-white bg-red-500 px-2 py-0.5 rounded-full">未完成</span>
{% endif %}
</div>
<p class="text-sm text-gray-600 mb-3">{{ huodong.description }}</p>
<div class="flex flex-wrap items-center gap-4 text-xs text-gray-500">
<span class="inline-flex items-center gap-1 bg-blue-50 text-blue-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ huodong.start_time|date:"Y年m月d日" }}
</span>
{% if huodong.end_time %}
<span class="inline-flex items-center gap-1 bg-green-50 text-green-700 px-2.5 py-1 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ huodong.end_time|date:"Y年m月d日" }}
</span>
{% endif %}
</div>
</div>
</div>
</div>
{% empty %}
<div class="p-8 text-center text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
暂无活动
</div>
{% endfor %}
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-gray-300 to-transparent"></div>
{% endfor %}
{% endblock %}

View File

@@ -1,67 +1,202 @@
{% extends 'base.html' %}
{% block content %}
<table class="items-center bg-transparent w-full border-collapse ">
<thead>
<tr class="bg-gray-100">
<th class="px-6 bg-blueGray-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-base uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-left">
分支机构名称
<br>
基础信息
</th>
<th class="px-6 bg-blueGray-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-base uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-left">
信息系统类别
</th>
</tr>
</thead>
<tbody>
{% for item in branches %}
<!-- 这里是其他行的处理逻辑 -->
<tr class="{% if forloop.counter|divisibleby:2 %}bg-gray-100{% else %}bg-white{% endif %}">
<td class="px-6 py-4 whitespace-no-wrap text-left border-b border-gray-200">
<a href="{% url 'branch-detail' item.pk %}"
class="bg-transparent hover:bg-blue-900 hover:text-white p-2 rounded-md">
{{ item.name }}
</a>
</td>
<td class="px-6 py-4 whitespace-no-wrap text-left border-b border-gray-200">
{{ item.category }}
</td>
<!-- 页面标题 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-indigo-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-indigo-600 to-purple-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构信息
</h1>
</div>
</div>
</div>
</tr>
{% endfor %}
</tbody>
<tr class="bg-gray-200 font-bold">
<td class="px-6 py-4 whitespace-no-wrap text-left border-b border-gray-200">
分支机构类别统计
</td>
<td class="px-6 py-4 whitespace-no-wrap text-left border-b border-gray-200">
A型: {{ type_a_count }}家 | B型: {{ type_b_count }}家 | C型: {{ type_c_count }}家
</td>
</tr>
<tfoot>
<tr class="bg-gray-50">
<td colspan="2" class="px-6 py-4 text-right border-b border-gray-200">
<div class="flex justify-end space-x-3">
<a href="{% url 'export-branches-xls' %}" class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-black font-medium rounded-lg shadow-md transition duration-150 ease-in-out transform hover:scale-105">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
<!-- 分公司/营业部数量统计 -->
<div class="w-full max-w-6xl mx-auto px-6 mt-2 mb-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-indigo-100">
<div class="px-6 py-4 bg-gradient-to-r from-indigo-600 via-indigo-700 to-purple-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
机构数量统计
</h2>
</div>
<div class="p-6">
<div class="flex flex-wrap items-center gap-x-6 gap-y-3 text-base">
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</span>
<span class="text-gray-600">分支机构总数</span>
<span class="text-xl font-bold text-gray-800">{{ all_branch_count }}</span>
<span class="text-gray-500"></span>
</div>
<span class="text-gray-300">|</span>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 14v3m4-3v3m4-3v3M3 21h18M3 10h18M3 7l9-4 9 4M4 10h16v11H4V10z" />
</svg>
</span>
<span class="text-gray-600">分公司数量</span>
<span class="text-xl font-bold text-emerald-700">{{ fen_company_count }}</span>
<span class="text-gray-500"></span>
</div>
<span class="text-gray-300">|</span>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</span>
<span class="text-gray-600">营业部数量</span>
<span class="text-xl font-bold text-amber-700">{{ yingyebu_count }}</span>
<span class="text-gray-500"></span>
</div>
</div>
{% if preparing_count > 0 %}
<div class="mt-4 pt-4 border-t border-gray-100 flex items-center gap-2">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-rose-400 to-pink-500 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<span class="text-gray-600">筹建中的分公司或营业部数量</span>
<span class="text-xl font-bold text-rose-600">{{ preparing_count }}</span>
<span class="text-gray-500"></span>
</div>
{% endif %}
</div>
</div>
</div>
<!-- 分支机构列表 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-indigo-100">
<div class="px-6 py-4 bg-gradient-to-r from-indigo-600 via-indigo-700 to-purple-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0121 18.382V7.618a1 1 0 01-.553-.894L15 7m0 13V7" />
</svg>
分支机构列表
</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-slate-50 to-gray-100 border-b-2 border-indigo-200">
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
所在省份
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构名称
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
信息系统类别
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for province, branches in branches_by_province.items %}
{% for item in branches %}
<tr class="{% if forloop.parentloop.counter|divisibleby:2 %}bg-slate-50/50{% else %}bg-white{% endif %} hover:bg-indigo-50/50 transition-colors duration-200">
{% if forloop.first %}
<td class="px-6 py-4 whitespace-nowrap text-left font-medium text-gray-800" rowspan="{{ branches|length }}">
<span class="inline-flex items-center px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-sm">
{{ province }}
</span>
</td>
{% endif %}
<td class="px-6 py-4 whitespace-nowrap text-left">
<a href="{% url 'branch-detail' item.pk %}"
class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium text-gray-700 bg-gray-100 hover:bg-indigo-600 hover:text-white transition-all duration-200">
{{ item.name }}
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-left">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium {% if item.category == 'A型' %}bg-blue-100 text-blue-700{% elif item.category == 'B型' %}bg-green-100 text-green-700{% elif item.category == 'C型' %}bg-orange-100 text-orange-700{% else %}bg-gray-100 text-gray-700{% endif %}">
{{ item.category }}
</span>
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
<!-- 统计行 -->
<tr class="bg-gradient-to-r from-indigo-50 to-purple-50 border-t-2 border-indigo-200">
<td class="px-6 py-4 whitespace-nowrap font-bold text-gray-800">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
分支机构类别统计
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap" colspan="2">
<div class="flex flex-wrap gap-3">
<span class="inline-flex items-center px-3 py-1.5 rounded-full bg-blue-100 text-blue-700 text-sm font-medium">
<span class="w-2 h-2 rounded-full bg-blue-500 mr-2"></span>
A型: {{ type_a_count }}家
</span>
<span class="inline-flex items-center px-3 py-1.5 rounded-full bg-green-100 text-green-700 text-sm font-medium">
<span class="w-2 h-2 rounded-full bg-green-500 mr-2"></span>
B型: {{ type_b_count }}家
</span>
<span class="inline-flex items-center px-3 py-1.5 rounded-full bg-orange-100 text-orange-700 text-sm font-medium">
<span class="w-2 h-2 rounded-full bg-orange-500 mr-2"></span>
C型: {{ type_c_count }}家
</span>
</div>
</td>
</tr>
</table>
</div>
<!-- 导出按钮 -->
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100">
<div class="flex justify-end gap-3">
<a href="{% url 'export-branches-xls' %}" class="inline-flex items-center px-5 py-2.5 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
导出为XLS
</a>
<a href="{% url 'export-branches-pdf' %}" class="inline-flex items-center px-4 py-2 bg-purple-600 hover:bg-purple-800 text-black font-medium rounded-lg shadow-md transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
<a href="{% url 'export-branches-pdf' %}" class="inline-flex items-center px-5 py-2.5 bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 text-white font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
导出为PDF
</a>
</div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-purple-400 to-transparent"></div>
<hr class="h-px my-8 bg-red-500 border-1 dark:bg-red-700">
{% endblock %}

View File

@@ -1,336 +1,444 @@
{% extends "base.html" %}
{% block content %}
<div class="container mx-auto p-4">
<h2 class="text-xl font-bold mb-4">联系人信息</h2>
<!-- 新增筛选表单 -->
<form method="GET" class="mb-4 p-4 bg-gray-100 rounded-lg" id="filterForm">
<div class="flex flex-wrap gap-4">
<!-- 分支机构筛选(带搜索的多选下拉框) -->
<div class="flex flex-col w-full md:w-1/2">
<label class="text-sm font-medium">分支机构(可多选)</label>
<div class="relative" id="branchSelectContainer">
<!-- 选择框主体 -->
<div class="relative">
<input
type="text"
id="branchSearchInput"
class="w-full pl-4 pr-10 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-300 focus:border-blue-500 outline-none text-sm"
placeholder="点击选择分支机构(可多选)..."
readonly
onclick="toggleBranchDropdown()"
>
<span class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 transition-transform duration-200" id="branchArrowIcon">
<i class="fa fa-chevron-down"></i>
</span>
<!-- 选中的机构显示 -->
<div id="selectedBranches" class="absolute z-5 mt-1 w-full bg-white rounded-lg shadow-md hidden max-h-32 overflow-y-auto">
<div class="p-2">
<div class="flex flex-wrap gap-1" id="branchTags"></div>
</div>
</div>
</div>
<!-- 下拉内容 -->
<div id="branchDropdownContent" class="absolute z-10 mt-1 w-full bg-white rounded-lg shadow-md overflow-hidden hidden">
<!-- 搜索输入框 -->
<div class="p-2 border-b border-gray-100">
<input
type="text"
id="branchFilterInput"
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-1 focus:ring-blue-300 focus:border-blue-500 outline-none text-sm"
placeholder="搜索分支机构..."
oninput="filterBranchOptions()"
autofocus
>
</div>
<!-- 选项列表 -->
<div id="branchOptionsList" class="max-h-60 overflow-y-auto">
<ul>
{% for branch in branches %}
<li class="branch-option px-4 py-2.5 hover:bg-gray-100 cursor-pointer text-sm transition-colors flex items-center"
data-id="{{ branch.id }}"
data-name="{{ branch.name }}">
<input type="checkbox" class="mr-2 branch-checkbox" value="{{ branch.id }}">
{{ branch.name }}
</li>
{% endfor %}
</ul>
</div>
<!-- 操作按钮 -->
<div class="p-2 border-t border-gray-100 flex gap-2">
<button type="button" class="flex-1 px-3 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600" onclick="selectAllBranches()">全选</button>
<button type="button" class="flex-1 px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600" onclick="clearAllBranches()">清空</button>
</div>
<!-- 独立的清空全部选择按钮 -->
<div class="p-2 border-t border-gray-100">
<button type="button" class="w-full px-3 py-2 text-sm bg-orange-500 text-white rounded-lg hover:bg-orange-600 font-medium" onclick="clearAllBranches()" title="清空所有机构选择">
<i class="fa fa-refresh mr-1"></i>清空全部选择
</button>
</div>
<!-- 无结果提示 -->
<div id="branchNoResult" class="px-4 py-4 text-center text-gray-500 text-sm hidden">
没有找到匹配的分支机构
</div>
</div>
</div>
</div>
<!-- 联系人类别筛选(保持原结构) -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium">联系人类别</label>
<input type="text" name="category" class="mt-1 p-2 border rounded" placeholder="输入类别名称筛选" value="{{ selected_category|default:'' }}">
</div>
<!-- 联系人姓名筛选(保持原结构) -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium">联系人姓名</label>
<input type="text" name="contact_name" class="mt-1 p-2 border rounded" placeholder="输入联系人姓名筛选" value="{{ selected_contact_name|default:'' }}">
</div>
<div class="flex items-end gap-2">
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">筛选</button>
<button type="button" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors duration-200 font-medium" onclick="clearFilters()">
<i class="fa fa-times mr-1"></i>清空筛选
</button>
</div>
</div>
<!-- 隐藏的分支机构ID输入框用于表单提交 -->
<input type="hidden" name="branches" id="selectedBranchesInput">
</form>
<table class="min-w-full border-collapse border border-gray-300">
<thead>
<tr>
<th class="p-2 border border-gray-300 bg-gray-100">分支机构</th>
<th class="p-2 border border-gray-300 bg-gray-100">分类</th>
<th class="p-2 border border-gray-300 bg-gray-100">姓名</th>
<th class="p-2 border border-gray-300 bg-gray-100">电话</th>
<th class="p-2 border border-gray-300 bg-gray-100">邮箱</th>
</tr>
</thead>
<tbody>
{% for contact in contacts %}
<tr class="{% cycle 'bg-white' 'bg-gray-50' %}">
<td class="p-2 border border-gray-300">{{ contact.branch.name }}</td>
<td class="p-2 border border-gray-300">{{ contact.category|default:"" }}</td>
<td class="p-2 border border-gray-300">{{ contact.name|default:"" }}</td>
<td class="p-2 border border-gray-300">{{ contact.phone|default:"" }}</td>
<td class="p-2 border border-gray-300">{{ contact.email|default:"" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="p-4 text-center text-gray-500">没有找到匹配的联系人</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
// 选中的分支机构
let selectedBranches = new Set();
// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
// 初始化选中的机构
initializeSelectedBranches();
// 绑定复选框事件
bindCheckboxEvents();
});
// 初始化选中的机构(从表单参数中读取)
function initializeSelectedBranches() {
const urlParams = new URLSearchParams(window.location.search);
const branchesParam = urlParams.get('branches');
if (branchesParam) {
const branchIds = branchesParam.split(',');
branchIds.forEach(id => {
selectedBranches.add(id);
const checkbox = document.querySelector(`input[value="${id}"]`);
if (checkbox) {
checkbox.checked = true;
}
});
}
updateBranchSelectionUI();
}
// 绑定复选框事件
function bindCheckboxEvents() {
document.querySelectorAll('.branch-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const branchId = this.value;
const branchName = this.closest('.branch-option').dataset.name;
if (this.checked) {
selectedBranches.add(branchId);
} else {
selectedBranches.delete(branchId);
}
updateBranchSelectionUI();
});
});
}
// 更新机构选择UI
function updateBranchSelectionUI() {
const selectedBranchesInput = document.getElementById('selectedBranchesInput');
const branchSearchInput = document.getElementById('branchSearchInput');
const selectedBranchesDiv = document.getElementById('selectedBranches');
const branchTags = document.getElementById('branchTags');
// 更新隐藏输入框
if (selectedBranches.size > 0) {
selectedBranchesInput.value = Array.from(selectedBranches).join(',');
branchSearchInput.placeholder = `已选择 ${selectedBranches.size} 个机构`;
} else {
selectedBranchesInput.value = '';
branchSearchInput.placeholder = '点击选择分支机构(可多选)...';
}
// 更新显示的标签
branchTags.innerHTML = '';
selectedBranches.forEach(branchId => {
const branchName = document.querySelector(`input[value="${branchId}"]`).closest('.branch-option').dataset.name;
const tag = document.createElement('span');
tag.className = 'inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800';
tag.innerHTML = `
${branchName}
<button type="button" class="ml-1 text-blue-600 hover:text-blue-800" onclick="removeBranch('${branchId}')">
<i class="fa fa-times"></i>
</button>
`;
branchTags.appendChild(tag);
});
// 控制选中机构显示区域
if (selectedBranches.size > 0) {
selectedBranchesDiv.classList.remove('hidden');
} else {
selectedBranchesDiv.classList.add('hidden');
}
}
// 移除机构
function removeBranch(branchId) {
selectedBranches.delete(branchId);
const checkbox = document.querySelector(`input[value="${branchId}"]`);
if (checkbox) {
checkbox.checked = false;
}
updateBranchSelectionUI();
}
// 全选分支机构
function selectAllBranches() {
selectedBranches.clear();
document.querySelectorAll('.branch-checkbox').forEach(checkbox => {
checkbox.checked = true;
selectedBranches.add(checkbox.value);
});
updateBranchSelectionUI();
}
// 清空所有分支机构选择
function clearAllBranches() {
selectedBranches.clear();
document.querySelectorAll('.branch-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
updateBranchSelectionUI();
}
// 清空所有筛选
function clearFilters() {
// 清空所有表单字段
document.getElementById('filterForm').reset();
// 清空机构选择
clearAllBranches();
// 提交清空后的表单
window.location.href = window.location.pathname;
}
// 控制分支机构下拉框显示/隐藏
function toggleBranchDropdown() {
const dropdown = document.getElementById('branchDropdownContent');
const arrow = document.getElementById('branchArrowIcon');
if (dropdown.classList.contains('hidden')) {
dropdown.classList.remove('hidden');
arrow.classList.add('rotate-180');
document.getElementById('branchFilterInput').focus();
document.addEventListener('click', handleBranchOutsideClick);
} else {
closeBranchDropdown();
}
}
// 关闭分支机构下拉框
function closeBranchDropdown() {
const dropdown = document.getElementById('branchDropdownContent');
const arrow = document.getElementById('branchArrowIcon');
dropdown.classList.add('hidden');
arrow.classList.remove('rotate-180');
document.removeEventListener('click', handleBranchOutsideClick);
}
// 点击外部关闭下拉框
function handleBranchOutsideClick(event) {
const container = document.getElementById('branchSelectContainer');
if (!container.contains(event.target)) {
closeBranchDropdown();
}
}
// 筛选分支机构选项
function filterBranchOptions() {
const searchText = document.getElementById('branchFilterInput').value.toLowerCase();
const options = document.querySelectorAll('.branch-option');
const noResult = document.getElementById('branchNoResult');
let hasMatch = false;
options.forEach(option => {
const name = option.dataset.name.toLowerCase();
if (name.includes(searchText)) {
option.style.display = 'block';
hasMatch = true;
} else {
option.style.display = 'none';
}
});
// 显示或隐藏无结果提示
if (searchText && !hasMatch) {
noResult.classList.remove('hidden');
} else {
noResult.classList.add('hidden');
}
}
// 选择分支机构选项(保留原有功能以兼容单选操作)
document.querySelectorAll('.branch-option').forEach(option => {
option.addEventListener('click', function(e) {
// 如果点击的是复选框,不重复处理
if (e.target.type === 'checkbox') {
return;
}
const checkbox = this.querySelector('.branch-checkbox');
checkbox.checked = !checkbox.checked;
// 触发复选框的change事件
checkbox.dispatchEvent(new Event('change'));
});
});
</script>
{% extends "base.html" %}
{% block content %}
<!-- 页面标题 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-teal-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-teal-600 to-cyan-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
联系人信息
</h1>
</div>
</div>
</div>
<div class="w-full max-w-6xl mx-auto p-6">
<!-- 筛选表单 -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-teal-100 mb-6">
<div class="px-6 py-4 bg-gradient-to-r from-teal-600 via-teal-700 to-cyan-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选条件
</h2>
</div>
<div class="p-6">
<form method="GET" id="filterForm">
<div class="flex flex-wrap gap-4">
<!-- 分支机构筛选(带搜索的多选下拉框) -->
<div class="flex flex-col w-full md:w-1/2">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构(可多选)
</label>
<div class="relative" id="branchSelectContainer">
<!-- 选择框主体 -->
<div class="relative">
<input
type="text"
id="branchSearchInput"
class="w-full pl-4 pr-10 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-teal-300 focus:border-teal-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors"
placeholder="点击选择分支机构(可多选)..."
readonly
onclick="toggleBranchDropdown()"
>
<span class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 transition-transform duration-200" id="branchArrowIcon">
<i class="fa fa-chevron-down"></i>
</span>
<!-- 选中的机构显示 -->
<div id="selectedBranches" class="absolute z-5 mt-1 w-full bg-white rounded-lg shadow-md hidden max-h-32 overflow-y-auto">
<div class="p-2">
<div class="flex flex-wrap gap-1" id="branchTags"></div>
</div>
</div>
</div>
<!-- 下拉内容 -->
<div id="branchDropdownContent" class="absolute z-10 mt-1 w-full bg-white rounded-lg shadow-lg overflow-hidden hidden border border-gray-100">
<!-- 搜索输入框 -->
<div class="p-2 border-b border-gray-100">
<input
type="text"
id="branchFilterInput"
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-1 focus:ring-teal-300 focus:border-teal-500 outline-none text-sm"
placeholder="搜索分支机构..."
oninput="filterBranchOptions()"
autofocus
>
</div>
<!-- 选项列表 -->
<div id="branchOptionsList" class="max-h-60 overflow-y-auto">
<ul>
{% for branch in branches %}
<li class="branch-option px-4 py-2.5 hover:bg-teal-50 cursor-pointer text-sm transition-colors flex items-center"
data-id="{{ branch.id }}"
data-name="{{ branch.name }}">
<input type="checkbox" class="mr-2 branch-checkbox rounded text-teal-600 focus:ring-teal-500" value="{{ branch.id }}">
{{ branch.name }}
</li>
{% endfor %}
</ul>
</div>
<!-- 操作按钮 -->
<div class="p-2 border-t border-gray-100 flex gap-2">
<button type="button" class="flex-1 px-3 py-1 text-xs bg-teal-500 text-white rounded hover:bg-teal-600 transition-colors" onclick="selectAllBranches()">全选</button>
<button type="button" class="flex-1 px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600 transition-colors" onclick="clearAllBranches()">清空</button>
</div>
<!-- 独立的清空全部选择按钮 -->
<div class="p-2 border-t border-gray-100">
<button type="button" class="w-full px-3 py-2 text-sm bg-orange-500 text-white rounded-lg hover:bg-orange-600 font-medium transition-colors" onclick="clearAllBranches()" title="清空所有机构选择">
<i class="fa fa-refresh mr-1"></i>清空全部选择
</button>
</div>
<!-- 无结果提示 -->
<div id="branchNoResult" class="px-4 py-4 text-center text-gray-500 text-sm hidden">
没有找到匹配的分支机构
</div>
</div>
</div>
</div>
<!-- 联系人类别筛选 -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
联系人类别
</label>
<input type="text" name="category" class="mt-1 p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-teal-300 focus:border-teal-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors" placeholder="输入类别名称筛选" value="{{ selected_category|default:'' }}">
</div>
<!-- 联系人姓名筛选 -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
联系人姓名
</label>
<input type="text" name="contact_name" class="mt-1 p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-teal-300 focus:border-teal-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors" placeholder="输入联系人姓名筛选" value="{{ selected_contact_name|default:'' }}">
</div>
<div class="flex items-end gap-2">
<button type="submit" class="px-5 py-2.5 bg-gradient-to-r from-teal-600 to-teal-700 hover:from-teal-700 hover:to-teal-800 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 font-medium">
<i class="fa fa-search mr-1"></i>筛选
</button>
<button type="button" class="px-5 py-2.5 bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 font-medium" onclick="clearFilters()">
<i class="fa fa-times mr-1"></i>清空筛选
</button>
</div>
</div>
<!-- 隐藏的分支机构ID输入框用于表单提交 -->
<input type="hidden" name="branches" id="selectedBranchesInput">
</form>
</div>
</div>
<!-- 联系人列表 -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-teal-100">
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-teal-50 to-cyan-50 border-b-2 border-teal-200">
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
分类
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
姓名
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
电话
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-teal-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
邮箱
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for contact in contacts %}
<tr class="{% cycle 'bg-white' 'bg-slate-50/50' %} hover:bg-teal-50/50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 font-medium">{{ contact.branch.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ contact.category|default:"-" }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">{{ contact.name|default:"-" }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ contact.phone|default:"-" }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ contact.email|default:"-" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="px-6 py-8 text-center text-gray-500">
<div class="flex flex-col items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>没有找到匹配的联系人</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- 导出按钮 -->
<div class="mt-6 flex justify-end gap-4">
<a href="{% url 'export-contacts-xls' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" class="inline-flex items-center px-5 py-2.5 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
导出为Excel
</a>
<a href="{% url 'export-contacts-pdf' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" class="inline-flex items-center px-5 py-2.5 bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 text-white font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
导出为PDF
</a>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-teal-400 to-transparent"></div>
<script>
// 选中的分支机构
let selectedBranches = new Set();
// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
// 初始化选中的机构
initializeSelectedBranches();
// 绑定复选框事件
bindCheckboxEvents();
});
// 初始化选中的机构(从表单参数中读取)
function initializeSelectedBranches() {
const urlParams = new URLSearchParams(window.location.search);
const branchesParam = urlParams.get('branches');
if (branchesParam) {
const branchIds = branchesParam.split(',');
branchIds.forEach(id => {
selectedBranches.add(id);
const checkbox = document.querySelector(`input[value="${id}"]`);
if (checkbox) {
checkbox.checked = true;
}
});
}
updateBranchSelectionUI();
}
// 绑定复选框事件
function bindCheckboxEvents() {
document.querySelectorAll('.branch-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const branchId = this.value;
const branchName = this.closest('.branch-option').dataset.name;
if (this.checked) {
selectedBranches.add(branchId);
} else {
selectedBranches.delete(branchId);
}
updateBranchSelectionUI();
});
});
}
// 更新机构选择UI
function updateBranchSelectionUI() {
const selectedBranchesInput = document.getElementById('selectedBranchesInput');
const branchSearchInput = document.getElementById('branchSearchInput');
const selectedBranchesDiv = document.getElementById('selectedBranches');
const branchTags = document.getElementById('branchTags');
// 更新隐藏输入框
if (selectedBranches.size > 0) {
selectedBranchesInput.value = Array.from(selectedBranches).join(',');
branchSearchInput.placeholder = `已选择 ${selectedBranches.size} 个机构`;
} else {
selectedBranchesInput.value = '';
branchSearchInput.placeholder = '点击选择分支机构(可多选)...';
}
// 更新显示的标签
branchTags.innerHTML = '';
selectedBranches.forEach(branchId => {
const branchName = document.querySelector(`input[value="${branchId}"]`).closest('.branch-option').dataset.name;
const tag = document.createElement('span');
tag.className = 'inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800';
tag.innerHTML = `
${branchName}
<button type="button" class="ml-1 text-blue-600 hover:text-blue-800" onclick="removeBranch('${branchId}')">
<i class="fa fa-times"></i>
</button>
`;
branchTags.appendChild(tag);
});
// 控制选中机构显示区域
if (selectedBranches.size > 0) {
selectedBranchesDiv.classList.remove('hidden');
} else {
selectedBranchesDiv.classList.add('hidden');
}
}
// 移除机构
function removeBranch(branchId) {
selectedBranches.delete(branchId);
const checkbox = document.querySelector(`input[value="${branchId}"]`);
if (checkbox) {
checkbox.checked = false;
}
updateBranchSelectionUI();
}
// 全选分支机构
function selectAllBranches() {
selectedBranches.clear();
document.querySelectorAll('.branch-checkbox').forEach(checkbox => {
checkbox.checked = true;
selectedBranches.add(checkbox.value);
});
updateBranchSelectionUI();
}
// 清空所有分支机构选择
function clearAllBranches() {
selectedBranches.clear();
document.querySelectorAll('.branch-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
updateBranchSelectionUI();
}
// 清空所有筛选
function clearFilters() {
// 清空所有表单字段
document.getElementById('filterForm').reset();
// 清空机构选择
clearAllBranches();
// 提交清空后的表单
window.location.href = window.location.pathname;
}
// 控制分支机构下拉框显示/隐藏
function toggleBranchDropdown() {
const dropdown = document.getElementById('branchDropdownContent');
const arrow = document.getElementById('branchArrowIcon');
if (dropdown.classList.contains('hidden')) {
dropdown.classList.remove('hidden');
arrow.classList.add('rotate-180');
document.getElementById('branchFilterInput').focus();
document.addEventListener('click', handleBranchOutsideClick);
} else {
closeBranchDropdown();
}
}
// 关闭分支机构下拉框
function closeBranchDropdown() {
const dropdown = document.getElementById('branchDropdownContent');
const arrow = document.getElementById('branchArrowIcon');
dropdown.classList.add('hidden');
arrow.classList.remove('rotate-180');
document.removeEventListener('click', handleBranchOutsideClick);
}
// 点击外部关闭下拉框
function handleBranchOutsideClick(event) {
const container = document.getElementById('branchSelectContainer');
if (!container.contains(event.target)) {
closeBranchDropdown();
}
}
// 筛选分支机构选项
function filterBranchOptions() {
const searchText = document.getElementById('branchFilterInput').value.toLowerCase();
const options = document.querySelectorAll('.branch-option');
const noResult = document.getElementById('branchNoResult');
let hasMatch = false;
options.forEach(option => {
const name = option.dataset.name.toLowerCase();
if (name.includes(searchText)) {
option.style.display = 'block';
hasMatch = true;
} else {
option.style.display = 'none';
}
});
// 显示或隐藏无结果提示
if (searchText && !hasMatch) {
noResult.classList.remove('hidden');
} else {
noResult.classList.add('hidden');
}
}
// 选择分支机构选项(保留原有功能以兼容单选操作)
document.querySelectorAll('.branch-option').forEach(option => {
option.addEventListener('click', function(e) {
// 如果点击的是复选框,不重复处理
if (e.target.type === 'checkbox') {
return;
}
const checkbox = this.querySelector('.branch-checkbox');
checkbox.checked = !checkbox.checked;
// 触发复选框的change事件
checkbox.dispatchEvent(new Event('change'));
});
});
</script>
{% endblock %}

View File

@@ -1,66 +1,186 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8 text-center">分支机构设备间图片列表</h1>
<div class="flex justify-center mb-6">
<form method="get" action="" class="w-full max-w-md">
<div class="flex">
<input type="text" name="search" placeholder="搜索分支机构名称..." value="{{ request.GET.search }}"
class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded-r-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
<!-- 页面标题 -->
<div class="w-full max-w-7xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-emerald-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-emerald-600 to-teal-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
分支机构设备间图片列表
</h1>
</div>
</div>
</div>
<div class="w-full max-w-7xl mx-auto p-6">
<!-- 搜索区域 -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-emerald-100 mb-6">
<div class="px-6 py-4 bg-gradient-to-r from-emerald-600 via-emerald-700 to-teal-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
搜索分支机构
</h2>
</div>
<div class="p-6">
<form method="get" action="" class="flex flex-col sm:flex-row gap-4 items-center justify-center">
<div class="relative flex-1 max-w-md w-full">
<input type="text" name="search" placeholder="搜索分支机构名称..." value="{{ request.GET.search }}"
class="w-full px-4 py-3 pl-12 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 bg-gray-50 hover:bg-white transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 absolute left-4 top-1/2 transform -translate-y-1/2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<button type="submit" class="px-6 py-3 bg-gradient-to-r from-emerald-600 to-emerald-700 hover:from-emerald-700 hover:to-emerald-800 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 font-medium flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
搜索
</button>
</div>
</form>
</form>
</div>
</div>
<!-- 统计信息 -->
<div class="mb-6 text-center">
<span class="inline-flex items-center px-4 py-2 rounded-full bg-emerald-100 text-emerald-700 text-sm font-medium">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
共 {{ total_count }} 个分支机构
</span>
</div>
<p class="text-gray-600 mb-6 text-center">共 {{ total_count }} 个分支机构</p>
{% if branches %}
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{% for branch in branches %}
<div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
<div class="p-4">
<h2 class="text-xl font-semibold text-gray-900 mb-1">{{ branch.name }}</h2>
<a href="{% url 'branch-detail' branch.id %}" class="text-blue-600 hover:text-blue-800 text-sm font-medium">
查看分支机构详情
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-emerald-100 hover:shadow-2xl transition-all duration-300 group">
<div class="p-5 bg-gradient-to-r from-emerald-50 to-teal-50 border-b border-emerald-100">
<h2 class="text-lg font-bold text-gray-900 mb-2">{{ branch.name }}</h2>
<a href="{% url 'branch-detail' branch.id %}" class="inline-flex items-center text-emerald-600 hover:text-emerald-800 text-sm font-medium transition-colors">
查看分支机构详情
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
</div>
<div class="bg-gray-100 p-2">
<!-- 设备间图片 -->
<div class="p-4 bg-gray-50">
<div class="flex items-center gap-2 mb-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span class="text-sm font-semibold text-gray-700">设备间图片</span>
</div>
{% if branch.equipment_images.exists %}
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2">
<div class="grid grid-cols-2 gap-2">
{% for image in branch.equipment_images.all %}
<img src="{{ image.image.url }}" alt="{{ branch.name }}设备间图片"
class="h-[100px] md:h-[150px] lg:h-[200px] w-auto object-cover cursor-pointer"
class="h-[80px] w-full object-cover rounded-lg cursor-pointer hover:scale-105 transition-transform duration-300"
data-branch-id="{{ branch.id }}" data-image-type="equipment" data-index="{{ forloop.counter0 }}"
onclick="openImageModal(this)">
{% endfor %}
</div>
{% else %}
<div class="flex items-center justify-center h-48 bg-gray-200">
<span class="text-gray-400 text-sm">无图片</span>
<div class="flex items-center justify-center h-24 bg-gray-100 rounded-lg">
<div class="text-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-300 mx-auto mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span class="text-gray-400 text-xs">无图片</span>
</div>
</div>
{% endif %}
</div>
<!-- 图纸显示区域 -->
<div class="bg-gray-100 p-2 mt-4">
<h3 class="text-lg font-semibold mb-2">图纸</h3>
<div class="p-4 bg-gray-50 border-t border-gray-100">
<div class="flex items-center gap-2 mb-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0121 18.382V7.618a1 1 0 01-.553-.894L15 7m0 13V7" />
</svg>
<span class="text-sm font-semibold text-gray-700">图纸</span>
</div>
{% if branch.drawings.exists %}
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2">
<div class="grid grid-cols-2 gap-2">
{% for drawing in branch.drawings.all %}
<img src="{{ drawing.image.url }}" alt="{{ branch.name }}图纸"
class="h-[100px] md:h-[150px] lg:h-[200px] w-auto object-cover cursor-pointer"
class="h-[80px] w-full object-cover rounded-lg cursor-pointer hover:scale-105 transition-transform duration-300"
data-branch-id="{{ branch.id }}" data-image-type="drawings" data-index="{{ forloop.counter0 }}"
onclick="openImageModal(this)">
{% endfor %}
</div>
{% else %}
<div class="flex items-center justify-center h-48 bg-gray-200">
<span class="text-gray-400 text-sm">无图纸</span>
<div class="flex items-center justify-center h-24 bg-gray-100 rounded-lg">
<div class="text-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-300 mx-auto mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0121 18.382V7.618a1 1 0 01-.553-.894L15 7m0 13V7" />
</svg>
<span class="text-gray-400 text-xs">无图纸</span>
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- 分页控件 -->
<div class="mt-10 flex justify-center">
<nav class="inline-flex rounded-xl shadow-lg overflow-hidden border border-emerald-100">
{% if branches.has_previous %}
<a href="?page=1" class="relative inline-flex items-center px-4 py-3 bg-white text-sm font-medium text-gray-700 hover:bg-emerald-50 transition-colors border-r border-gray-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
</svg>
首页
</a>
<a href="?page={{ branches.previous_page_number }}" class="relative inline-flex items-center px-4 py-3 bg-white text-sm font-medium text-gray-700 hover:bg-emerald-50 transition-colors border-r border-gray-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
上一页
</a>
{% endif %}
<span class="relative inline-flex items-center px-6 py-3 bg-gradient-to-r from-emerald-50 to-teal-50 text-sm font-bold text-emerald-700">
第 {{ branches.number }} 页,共 {{ branches.paginator.num_pages }} 页
</span>
{% if branches.has_next %}
<a href="?page={{ branches.next_page_number }}" class="relative inline-flex items-center px-4 py-3 bg-white text-sm font-medium text-gray-700 hover:bg-emerald-50 transition-colors border-l border-gray-100">
下一页
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
<a href="?page={{ branches.paginator.num_pages }}" class="relative inline-flex items-center px-4 py-3 bg-white text-sm font-medium text-gray-700 hover:bg-emerald-50 transition-colors border-l border-gray-100">
末页
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
</svg>
</a>
{% endif %}
</nav>
</div>
{% else %}
<div class="text-center py-12 bg-white rounded-2xl shadow-lg border border-emerald-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-300 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p class="text-gray-500 text-lg">暂无分支机构设备间图片数据</p>
</div>
{% endif %}
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-emerald-400 to-transparent"></div>
<!-- 图片查看模态框 -->
<div id="imageModal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
@@ -135,9 +255,7 @@ document.getElementById('imageModal').addEventListener('click', function(e) {
});
</script>
</div>
{% endfor %}
</div>
<!-- 分页控件 -->
<div class="mt-10 flex justify-center">
@@ -165,10 +283,6 @@ document.getElementById('imageModal').addEventListener('click', function(e) {
{% endif %}
</nav>
</div>
{% else %}
<div class="text-center py-12 bg-gray-50 rounded-lg">
<p class="text-gray-500 text-lg">暂无分支机构设备间图片数据</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -2,78 +2,146 @@
{% load static %}
{% block content %}
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-6">公共电子屏列表</h1>
<!-- 页面标题 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-cyan-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-cyan-600 to-blue-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
公共电子屏列表
</h1>
</div>
</div>
</div>
<div class="w-full max-w-6xl mx-auto p-6">
<!-- 筛选区域 -->
<div class="bg-gray-100 p-4 rounded-lg mb-6">
<form method="get" class="flex flex-col md:flex-row gap-4">
<div>
<label class="block mb-2">分支机构</label>
<select name="branch" class="border rounded p-2 w-full md:w-48">
<option value="">所有分支机构</option>
{% for branch in branches %}
<option value="{{ branch.id }}" {% if request.GET.branch == branch.id|stringformat:"i" %}selected{% endif %}>{{ branch.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block mb-2">屏幕类型</label>
<select name="screen_type" class="border rounded p-2 w-full md:w-48">
<option value="">所有类型</option>
<option value="marquee" {% if request.GET.screen_type == 'marquee' %}selected{% endif %}>跑马灯</option>
<option value="advertisement" {% if request.GET.screen_type == 'advertisement' %}selected{% endif %}>广告屏</option>
<option value="information" {% if request.GET.screen_type == 'information' %}selected{% endif %}>信息发布屏</option>
</select>
</div>
<div class="self-end">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">筛选</button>
{% if request.GET.branch or request.GET.screen_type %}
<a href="{% url 'public-screens' %}" class="ml-2 text-gray-600">清除筛选</a>
{% endif %}
</div>
</form>
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-cyan-100 mb-6">
<div class="px-6 py-4 bg-gradient-to-r from-cyan-600 via-cyan-700 to-blue-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选条件
</h2>
</div>
<div class="p-6">
<form method="get" class="flex flex-col md:flex-row gap-4 items-end">
<div class="flex flex-col w-full md:w-48">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-cyan-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构
</label>
<select name="branch" class="p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-cyan-300 focus:border-cyan-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors">
<option value="">所有分支机构</option>
{% for branch in branches %}
<option value="{{ branch.id }}" {% if request.GET.branch == branch.id|stringformat:"i" %}selected{% endif %}>{{ branch.name }}</option>
{% endfor %}
</select>
</div>
<div class="flex flex-col w-full md:w-48">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-cyan-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
屏幕类型
</label>
<select name="screen_type" class="p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-cyan-300 focus:border-cyan-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors">
<option value="">所有类型</option>
<option value="marquee" {% if request.GET.screen_type == 'marquee' %}selected{% endif %}>跑马灯</option>
<option value="advertisement" {% if request.GET.screen_type == 'advertisement' %}selected{% endif %}>广告屏</option>
<option value="information" {% if request.GET.screen_type == 'information' %}selected{% endif %}>信息发布屏</option>
</select>
</div>
<div class="flex items-center gap-2">
<button type="submit" class="px-5 py-2.5 bg-gradient-to-r from-cyan-600 to-cyan-700 hover:from-cyan-700 hover:to-cyan-800 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 font-medium flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选
</button>
{% if request.GET.branch or request.GET.screen_type %}
<a href="{% url 'public-screens' %}" class="px-4 py-2.5 text-gray-600 hover:text-cyan-600 hover:bg-cyan-50 rounded-lg transition-colors font-medium">
清除筛选
</a>
{% endif %}
</div>
</form>
</div>
</div>
<!-- 电子屏列表 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for screen in public_screens %}
<div class="border rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow">
<div class="bg-white rounded-2xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 border border-cyan-100 group">
<div class="relative">
{% if screen.image %}
<img src="{{ screen.image.url }}" alt="{{ screen.get_screen_type_display }}" class="w-full h-[150px] object-cover cursor-pointer" onclick="openModal('{{ screen.image.url }}')">
<img src="{{ screen.image.url }}" alt="{{ screen.get_screen_type_display }}" class="w-full h-[180px] object-cover cursor-pointer group-hover:scale-105 transition-transform duration-500" onclick="openModal('{{ screen.image.url }}')">
{% else %}
<div class="w-full h-[150px] bg-gray-200 flex items-center justify-center">
<span class="text-gray-500">暂无图片</span>
<div class="w-full h-[180px] bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center">
<div class="text-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-400 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span class="text-gray-500 text-sm">暂无图片</span>
</div>
</div>
{% endif %}
<div class="absolute top-2 right-2 bg-black bg-opacity-50 text-white px-2 py-1 rounded text-sm">
<div class="absolute top-3 right-3 bg-gradient-to-r from-cyan-600 to-blue-600 text-white px-3 py-1 rounded-full text-xs font-medium shadow-md">
{{ screen.get_screen_type_display }}
</div>
</div>
<div class="p-4">
<h3 class="font-semibold text-lg mb-2">{{ screen.branch.name }}</h3>
<div class="p-5">
<h3 class="font-bold text-lg mb-2 text-gray-800">{{ screen.branch.name }}</h3>
{% if screen.description %}
<p class="text-gray-700 mb-2">{{ screen.description|truncatechars:100 }}</p>
<p class="text-gray-600 mb-3 text-sm leading-relaxed">{{ screen.description|truncatechars:100 }}</p>
{% endif %}
<div class="text-sm text-gray-500">
<div class="text-sm space-y-1">
{% if screen.last_drill %}
<p>最后演练: {{ screen.last_drill.date|date:"Y-m-d H:i" }}</p>
<p class="flex items-center gap-2 text-gray-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
最后演练: {{ screen.last_drill.date|date:"Y-m-d H:i" }}
</p>
{% else %}
<p class="text-yellow-500">暂无演练记录</p>
<p class="flex items-center gap-2 text-amber-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
暂无演练记录
</p>
{% endif %}
<p>更新时间: {{ screen.updated_at|date:"Y-m-d H:i" }}</p>
<p class="flex items-center gap-2 text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
更新时间: {{ screen.updated_at|date:"Y-m-d H:i" }}
</p>
</div>
</div>
</div>
{% empty %}
<div class="col-span-full text-center py-10">
<p class="text-gray-500">暂无公共电子屏记录</p>
<div class="col-span-full text-center py-12">
<div class="bg-white rounded-2xl shadow-lg p-8 border border-cyan-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-300 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<p class="text-gray-500 text-lg">暂无公共电子屏记录</p>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-cyan-400 to-transparent"></div>
<!-- 图片预览模态框 -->
<div id="imageModal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
<button onclick="closeModal()" class="absolute top-4 right-4 text-white text-2xl">&times;</button>

View File

@@ -1,79 +1,189 @@
{% extends "base.html" %}
{% load custom_filters %}
{% block content %}
<div class="container mx-auto p-4">
<!-- 筛选表单 -->
<form method="GET" class="mb-8 p-4 bg-gray-100 rounded-lg">
<div class="flex flex-wrap gap-4">
<!-- 年份选择 -->
<div class="flex flex-col">
<label class="text-sm font-medium">选择年份</label>
<select name="year" class="mt-1 p-2 border rounded">
<option value="all">全部年份</option>
<option value="2023" {% if selected_year == '2023' %}selected{% endif %}>2023年</option>
<option value="2024" {% if selected_year == '2024' %}selected{% endif %}>2024年</option>
<option value="2025" {% if selected_year == '2025' %}selected{% endif %}>2025年</option>
</select>
</div>
<!-- 分支机构选择 -->
<div class="flex flex-col">
<label class="text-sm font-medium">分支机构</label>
<select name="branch" class="mt-1 p-2 border rounded">
<option value="all" {% if selected_branch == 'all' %}selected{% endif %}>全部分支机构</option>
{% for branch in branches %}
<option value="{{ branch.id }}" {% if selected_branch == branch.id|stringformat:"s" %}selected{% endif %}>
{{ branch.name }}
</option>
{% endfor %}
</select>
</div>
<!-- 状态选择 -->
<div class="flex flex-col">
<label class="text-sm font-medium">活动状态</label>
<select name="status" class="mt-1 p-2 border rounded">
<option value="completed" {% if selected_status == 'completed' %}selected{% endif %}>已完成</option>
<option value="ongoing" {% if selected_status == 'ongoing' %}selected{% endif %}>未完成</option>
</select>
</div>
<button type="submit" class="self-end px-4 py-2 bg-blue-200 text-white rounded hover:bg-blue-700">
筛选
</button>
</div>
</form>
<!-- 统计结果展示 -->
<div class="mt-4">
<h2 class="text-xl font-bold mb-4">活动统计结果(共{{ activities.count }}条)</h2>
<table class="min-w-full border-collapse border border-gray-300">
<thead>
<tr>
<th class="p-2 border border-gray-300 bg-gray-100">活动名称</th>
<th class="p-2 border border-gray-300 bg-gray-100">分支机构</th>
<th class="p-2 border border-gray-300 bg-gray-100">活动分类</th>
<th class="p-2 border border-gray-300 bg-gray-100">开始时间</th>
<th class="p-2 border border-gray-300 bg-gray-100">结束时间</th>
</tr>
</thead>
<tbody>
{% for activity in activities %}
<tr>
<td class="p-2 border border-gray-300">{{ activity.name }}</td>
<td class="p-2 border border-gray-300">{{ activity.branch.name }}</td>
<td class="p-2 border border-gray-300">{{ activity.scope }}</td>
<td class="p-2 border border-gray-300">{{ activity.start_time|format_chinese_full_date }}</td>
<td class="p-2 border border-gray-300">{{ activity.end_time|format_chinese_full_date|default:"未结束" }}</td>
</tr>
{% empty %}
<tr>
<td class="p-2 border border-gray-300 text-center" colspan="4">无符合条件的活动记录</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% extends "base.html" %}
{% load custom_filters %}
{% block content %}
<!-- 页面标题 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-rose-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-rose-600 to-pink-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
活动统计
</h1>
</div>
</div>
</div>
<div class="w-full max-w-6xl mx-auto p-6">
<!-- 筛选表单 -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-rose-100 mb-6">
<div class="px-6 py-4 bg-gradient-to-r from-rose-600 via-rose-700 to-pink-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选条件
</h2>
</div>
<div class="p-6">
<form method="GET" class="flex flex-wrap gap-4 items-end">
<!-- 年份选择 -->
<div class="flex flex-col">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
选择年份
</label>
<select name="year" class="mt-1 p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-rose-300 focus:border-rose-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors">
<option value="all">全部年份</option>
<option value="2023" {% if selected_year == '2023' %}selected{% endif %}>2023年</option>
<option value="2024" {% if selected_year == '2024' %}selected{% endif %}>2024年</option>
<option value="2025" {% if selected_year == '2025' %}selected{% endif %}>2025年</option>
</select>
</div>
<!-- 分支机构选择 -->
<div class="flex flex-col">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构
</label>
<select name="branch" class="mt-1 p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-rose-300 focus:border-rose-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors">
<option value="all" {% if selected_branch == 'all' %}selected{% endif %}>全部分支机构</option>
{% for branch in branches %}
<option value="{{ branch.id }}" {% if selected_branch == branch.id|stringformat:"s" %}selected{% endif %}>
{{ branch.name }}
</option>
{% endfor %}
</select>
</div>
<!-- 状态选择 -->
<div class="flex flex-col">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
活动状态
</label>
<select name="status" class="mt-1 p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-rose-300 focus:border-rose-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors">
<option value="completed" {% if selected_status == 'completed' %}selected{% endif %}>已完成</option>
<option value="ongoing" {% if selected_status == 'ongoing' %}selected{% endif %}>未完成</option>
</select>
</div>
<button type="submit" class="px-5 py-2.5 bg-gradient-to-r from-rose-600 to-rose-700 hover:from-rose-700 hover:to-rose-800 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 font-medium flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选
</button>
</form>
</div>
</div>
<!-- 统计结果展示 -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-rose-100">
<div class="px-6 py-4 bg-gradient-to-r from-rose-50 to-pink-50 border-b-2 border-rose-200">
<h2 class="text-lg font-bold text-gray-800 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-rose-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
活动统计结果
<span class="ml-2 px-3 py-1 bg-rose-100 text-rose-700 rounded-full text-sm font-medium">共 {{ activities.count }} 条</span>
</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-rose-50 to-pink-50 border-b-2 border-rose-200">
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
活动名称
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
活动分类
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
开始时间
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
结束时间
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for activity in activities %}
<tr class="{% cycle 'bg-white' 'bg-rose-50/30' %} hover:bg-rose-50/50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 font-medium">{{ activity.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ activity.branch.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-rose-100 text-rose-700">
{{ activity.scope }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ activity.start_time|format_chinese_full_date }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
{% if activity.end_time %}
{{ activity.end_time|format_chinese_full_date }}
{% else %}
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700">
未结束
</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="px-6 py-8 text-center text-gray-500">
<div class="flex flex-col items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>无符合条件的活动记录</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-rose-400 to-transparent"></div>
{% endblock %}

View File

@@ -1,94 +1,178 @@
{% extends "base.html" %}
{% block content %}
<div class="container mx-auto p-4">
<h2 class="text-xl font-bold mb-4">视频设备终端列表</h2>
<!-- 筛选表单 -->
<form method="GET" class="mb-4 p-4 bg-gray-100 rounded-lg">
<div class="flex flex-wrap gap-4">
<!-- 分支机构筛选 -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium">分支机构</label>
<div x-data="{ open: false, search: '{{ selected_branch_name|default:'' }}', selectedId: '{{ selected_branch|default:'' }}', branches: [{% for branch in branches %}{id: {{ branch.id }}, name: '{{ branch.name }}'}{% if not forloop.last %},{% endif %}{% endfor %}] }" class="relative">
<input type="text"
x-model="search"
@focus="open = true"
@input.debounce.300ms="open = true"
@click.away="open = false"
placeholder="搜索分支机构..."
class="mt-1 p-2 border rounded w-full"
x-on:keydown.backspace="if (!search) selectedId = ''; search = ''"
>
<div x-show="open" class="absolute z-10 mt-1 w-full bg-white border rounded shadow-lg max-h-60 overflow-y-auto">
<div @click="selectedId = ''; search = ''; open = false" class="p-2 hover:bg-gray-100 cursor-pointer">全部分支机构</div>
<div x-for="branch in branches.filter(b => b.name.toLowerCase().includes(search.toLowerCase()))"
:key="branch.id"
@click="selectedId = branch.id; search = branch.name; open = false"
class="p-2 hover:bg-gray-100 cursor-pointer"
x-text="branch.name"
></div>
</div>
<input type="hidden" name="branch" x-model="selectedId">
</div>
</div>
<!-- 设备类型筛选 -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium">设备类型</label>
<div x-data="{ open: false, search: '{{ selected_type_name|default:'' }}', selectedCode: '{{ selected_type|default:'' }}', types: [{% for type_code, type_name in terminal_types %}{code: '{{ type_code }}', name: '{{ type_name }}'}{% if not forloop.last %},{% endif %}{% endfor %}] }" class="relative">
<input type="text"
x-model="search"
@focus="open = true"
@input.debounce.300ms="open = true"
@click.away="open = false"
placeholder="搜索设备类型..."
class="mt-1 p-2 border rounded w-full"
x-on:keydown.backspace="if (!search) selectedCode = ''; search = ''"
>
<div x-show="open" class="absolute z-10 mt-1 w-full bg-white border rounded shadow-lg max-h-60 overflow-y-auto">
<div @click="selectedCode = ''; search = ''; open = false" class="p-2 hover:bg-gray-100 cursor-pointer">全部类型</div>
<div x-for="type in types.filter(t => t.name.toLowerCase().includes(search.toLowerCase()))"
:key="type.code"
@click="selectedCode = type.code; search = type.name; open = false"
class="p-2 hover:bg-gray-100 cursor-pointer"
x-text="type.name"
></div>
</div>
<input type="hidden" name="type" x-model="selectedCode">
</div>
</div>
<div class="flex items-end">
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">筛选</button>
</div>
<!-- 页面标题 -->
<div class="w-full max-w-6xl mx-auto p-6">
<div class="relative flex items-center justify-center py-8">
<div class="absolute left-0 right-0 h-1 bg-gradient-to-r from-transparent via-violet-500 to-transparent"></div>
<div class="relative z-10 bg-gradient-to-r from-violet-600 to-purple-700 text-white px-8 py-3 rounded-full shadow-lg">
<h1 class="text-xl font-bold flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
视频设备终端列表
</h1>
</div>
</form>
</div>
</div>
<div class="w-full max-w-6xl mx-auto p-6">
<!-- 筛选表单 -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-violet-100 mb-6">
<div class="px-6 py-4 bg-gradient-to-r from-violet-600 via-violet-700 to-purple-700">
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选条件
</h2>
</div>
<div class="p-6">
<form method="GET" class="flex flex-wrap gap-4 items-end">
<!-- 分支机构筛选 -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构
</label>
<div x-data="{ open: false, search: '{{ selected_branch_name|default:'' }}', selectedId: '{{ selected_branch|default:'' }}', branches: [{% for branch in branches %}{id: {{ branch.id }}, name: '{{ branch.name }}'}{% if not forloop.last %},{% endif %}{% endfor %}] }" class="relative">
<input type="text"
x-model="search"
@focus="open = true"
@input.debounce.300ms="open = true"
@click.away="open = false"
placeholder="搜索分支机构..."
class="w-full p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-violet-300 focus:border-violet-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors"
x-on:keydown.backspace="if (!search) selectedId = ''; search = ''"
>
<div x-show="open" class="absolute z-10 mt-1 w-full bg-white border border-gray-100 rounded-lg shadow-lg max-h-60 overflow-y-auto">
<div @click="selectedId = ''; search = ''; open = false" class="p-2 hover:bg-violet-50 cursor-pointer">全部分支机构</div>
<div x-for="branch in branches.filter(b => b.name.toLowerCase().includes(search.toLowerCase()))"
:key="branch.id"
@click="selectedId = branch.id; search = branch.name; open = false"
class="p-2 hover:bg-violet-50 cursor-pointer"
x-text="branch.name"
></div>
</div>
<input type="hidden" name="branch" x-model="selectedId">
</div>
</div>
<!-- 设备类型筛选 -->
<div class="flex flex-col w-full md:w-1/3">
<label class="text-sm font-medium text-gray-700 mb-2 flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
设备类型
</label>
<div x-data="{ open: false, search: '{{ selected_type_name|default:'' }}', selectedCode: '{{ selected_type|default:'' }}', types: [{% for type_code, type_name in terminal_types %}{code: '{{ type_code }}', name: '{{ type_name }}'}{% if not forloop.last %},{% endif %}{% endfor %}] }" class="relative">
<input type="text"
x-model="search"
@focus="open = true"
@input.debounce.300ms="open = true"
@click.away="open = false"
placeholder="搜索设备类型..."
class="w-full p-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-violet-300 focus:border-violet-500 outline-none text-sm bg-gray-50 hover:bg-white transition-colors"
x-on:keydown.backspace="if (!search) selectedCode = ''; search = ''"
>
<div x-show="open" class="absolute z-10 mt-1 w-full bg-white border border-gray-100 rounded-lg shadow-lg max-h-60 overflow-y-auto">
<div @click="selectedCode = ''; search = ''; open = false" class="p-2 hover:bg-violet-50 cursor-pointer">全部类型</div>
<div x-for="type in types.filter(t => t.name.toLowerCase().includes(search.toLowerCase()))"
:key="type.code"
@click="selectedCode = type.code; search = type.name; open = false"
class="p-2 hover:bg-violet-50 cursor-pointer"
x-text="type.name"
></div>
</div>
<input type="hidden" name="type" x-model="selectedCode">
</div>
</div>
<div class="flex items-end">
<button type="submit" class="px-5 py-2.5 bg-gradient-to-r from-violet-600 to-violet-700 hover:from-violet-700 hover:to-violet-800 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 font-medium flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
筛选
</button>
</div>
</form>
</div>
</div>
<!-- 终端列表表格 -->
<table class="min-w-full border-collapse border border-gray-300">
<thead>
<tr>
<th class="p-2 border border-gray-300 bg-gray-100">分支机构</th>
<th class="p-2 border border-gray-300 bg-gray-100">设备类型</th>
<th class="p-2 border border-gray-300 bg-gray-100">设备描述</th>
<th class="p-2 border border-gray-300 bg-gray-100">创建时间</th>
</tr>
</thead>
<tbody>
{% for terminal in terminals %}
<tr class="{% cycle 'bg-white' 'bg-gray-50' %}">
<td class="p-2 border border-gray-300">{{ terminal.branch.name }}</td>
<td class="p-2 border border-gray-300">{{ terminal.get_terminal_type_display }}</td>
<td class="p-2 border border-gray-300">{{ terminal.description|default:"" }}</td>
<td class="p-2 border border-gray-300">{{ terminal.created_at|date:"Y-m-d H:i" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="p-4 text-center text-gray-500">没有找到匹配的视频设备终端</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="bg-white rounded-2xl shadow-lg overflow-hidden border border-violet-100">
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-violet-50 to-purple-50 border-b-2 border-violet-200">
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
分支机构
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
设备类型
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
设备描述
</div>
</th>
<th class="px-6 py-4 text-left text-sm font-bold text-gray-700 uppercase tracking-wider">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-violet-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
创建时间
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for terminal in terminals %}
<tr class="{% cycle 'bg-white' 'bg-violet-50/30' %} hover:bg-violet-50/50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 font-medium">{{ terminal.branch.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-violet-100 text-violet-700">
{{ terminal.get_terminal_type_display }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ terminal.description|default:"-" }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ terminal.created_at|date:"Y-m-d H:i" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="px-6 py-8 text-center text-gray-500">
<div class="flex flex-col items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>没有找到匹配的视频设备终端</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- 分隔线 -->
<div class="h-1 my-8 rounded-full bg-gradient-to-r from-transparent via-violet-400 to-transparent"></div>
{% endblock %}

View File

@@ -28,3 +28,47 @@ def format_chinese_full_date(value):
weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
weekday = weekdays[date_obj.weekday()]
return f"{date_obj.year}{date_obj.month}{date_obj.day}{weekday}"
@register.filter
def get_current_year(value=None):
"""
获取当前年份
"""
# 只有当value是datetime对象时才使用它否则使用当前时间
if isinstance(value, datetime):
now = value
else:
now = datetime.now()
return now.year
@register.filter
def get_statistic_period(value=None):
"""
计算统计周期
规则:
- 在2026年1月实际统计的是2025年11月至2026年1月
- 在2026年2月实际统计的是2025年12月至2026年2月
- 以此类推
"""
# 只有当value是datetime对象时才使用它否则使用当前时间
if isinstance(value, datetime):
now = value
else:
now = datetime.now()
current_year = now.year
current_month = now.month
# 计算统计周期的开始月份和年份
if current_month <= 2:
start_year = current_year - 1
start_month = 11 + current_month
else:
start_year = current_year
start_month = current_month - 2
# 构建统计周期字符串
period = f"{start_year}{start_month}月至{current_year}{current_month}"
return period

View File

@@ -15,6 +15,8 @@ urlpatterns = [
path('branch/info/', views.Branchinfo, name='branchinfo'),
path('statistics/', views.Statistics, name='statistics'),
path('contact/', views.contact_list, name='contact-list'),
path('contact/export/pdf/', views.export_contacts_pdf, name='export-contacts-pdf'),
path('contact/export/xls/', views.export_contacts_xls, name='export-contacts-xls'),
path('equipment-images/', views.equipment_images, name='equipment-images'),
path('public-screens/', views.public_screens, name='public-screens'),
path('video-terminals/', views.video_terminal_list, name='video-terminals'),

View File

@@ -13,7 +13,7 @@ from django.http import HttpResponse
import openpyxl
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
import os
@@ -135,6 +135,8 @@ def BranchAll(request):
total_branch_count = len(branches_with_counts)
# 获取所有分支机构总数(包括没有活动的)
all_branch_count = Branch.objects.count()
fen_company_count = Branch.objects.filter(name__contains='分公司').count()
yingyebu_count = Branch.objects.filter(name__contains='营业部').count()
# 比如说end_time()的year是2024年但是now()是2025年的2月fileter出来但是如果now()是2025年3月则不显示
now = datetime.now()
print(f"当前月份是{now.month},年是{now.year}")
@@ -215,6 +217,8 @@ def BranchAll(request):
'latest_act': latest_act,
'total_branch_count':total_branch_count,
'all_branch_count': all_branch_count,
'fen_company_count': fen_company_count,
'yingyebu_count': yingyebu_count,
'branch_count': branch_count,
'ongoing_activities': ongoing_activities,
'total_activities': total_activities,
@@ -228,16 +232,28 @@ def BranchAll(request):
# 生成branchinfo的视图
def Branchinfo(request):
branches = Branch.objects.all()
# 统计A型、B型、C型分支机构的数量
branches = Branch.objects.all().order_by('location', 'name')
from collections import defaultdict
branches_by_province = defaultdict(list)
for branch in branches:
branches_by_province[branch.location].append(branch)
branches_by_province = dict(sorted(branches_by_province.items()))
type_a_count = branches.filter(category='A型').count()
type_b_count = branches.filter(category='B型').count()
type_c_count = branches.filter(category='C型').count()
all_branch_count = branches.count()
fen_company_count = branches.filter(name__contains='分公司').exclude(name__contains='(筹)').count()
yingyebu_count = branches.filter(name__contains='营业部').exclude(name__contains='(筹)').count()
preparing_count = branches.filter(name__contains='(筹)').count()
context = {
'branches': branches,
'branches_by_province': branches_by_province,
'type_a_count': type_a_count,
'type_b_count': type_b_count,
'type_c_count': type_c_count,
'all_branch_count': all_branch_count,
'fen_company_count': fen_company_count,
'yingyebu_count': yingyebu_count,
'preparing_count': preparing_count,
}
return render(request, 'branch_info.html', context)
@@ -286,17 +302,74 @@ def export_branches_xls(request):
worksheet.title = "分支机构信息"
# 添加表头
headers = ['分支机构名称', '信息系统类别']
headers = ['所在省份', '分支机构名称', '信息系统类别']
for col_num, header in enumerate(headers, 1):
worksheet.cell(row=1, column=col_num, value=header)
# 获取所有分支机构数据
branches = Branch.objects.all()
# 获取所有分支机构数据并按省份分组
branches = Branch.objects.all().order_by('location', 'name')
from collections import defaultdict
branches_by_province = defaultdict(list)
for branch in branches:
branches_by_province[branch.location].append(branch)
branches_by_province = dict(sorted(branches_by_province.items()))
# 填充数据
for row_num, branch in enumerate(branches, 2):
worksheet.cell(row=row_num, column=1, value=branch.name)
worksheet.cell(row=row_num, column=2, value=branch.category)
row_num = 2
for province, province_branches in branches_by_province.items():
# 添加省份标题行
worksheet.cell(row=row_num, column=1, value=f"{province}{len(province_branches)}")
worksheet.cell(row=row_num, column=2, value="")
worksheet.cell(row=row_num, column=3, value="")
# 设置省份标题行样式
province_row = worksheet[row_num]
for cell in province_row:
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
row_num += 1
# 填充该省份的分支机构数据
for branch in province_branches:
worksheet.cell(row=row_num, column=1, value=branch.location)
worksheet.cell(row=row_num, column=2, value=branch.name)
worksheet.cell(row=row_num, column=3, value=branch.category)
row_num += 1
row_num += 1 # 空行分隔
# 添加统计信息
row_num += 1
worksheet.cell(row=row_num, column=1, value="省份统计")
worksheet.cell(row=row_num, column=2, value=f"总计 {len(branches)} 个分支机构,分布在 {len(branches_by_province)} 个省份")
worksheet.cell(row=row_num, column=3, value="")
# 设置统计行样式
stats_row = worksheet[row_num]
for cell in stats_row:
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="FFD700", end_color="FFD700", fill_type="solid")
# 添加类别统计
row_num += 1
type_a_count = branches.filter(category='A型').count()
type_b_count = branches.filter(category='B型').count()
type_c_count = branches.filter(category='C型').count()
worksheet.cell(row=row_num, column=1, value="类别统计")
worksheet.cell(row=row_num, column=2, value=f"A型: {type_a_count}家 | B型: {type_b_count}家 | C型: {type_c_count}")
worksheet.cell(row=row_num, column=3, value="")
# 设置类别统计行样式
category_row = worksheet[row_num]
for cell in category_row:
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="FFD700", end_color="FFD700", fill_type="solid")
# 设置列宽
worksheet.column_dimensions['A'].width = 25
worksheet.column_dimensions['B'].width = 40
worksheet.column_dimensions['C'].width = 15
# 设置响应
response = HttpResponse(content_type='application/vnd.ms-excel')
@@ -349,13 +422,28 @@ def export_branches_pdf(request):
title = Paragraph("分支机构信息", title_style)
elements.append(title)
# 获取所有分支机构数据
branches = Branch.objects.all()
# 获取所有分支机构数据并按省份分组
branches = Branch.objects.all().order_by('location', 'name')
from collections import defaultdict
branches_by_province = defaultdict(list)
for branch in branches:
branches_by_province[branch.location].append(branch)
branches_by_province = dict(sorted(branches_by_province.items()))
# 准备表格数据
data = [['分支机构名称', '信息系统类别']] # 表头
for branch in branches:
data.append([branch.name, branch.category])
data = [['所在省份', '分支机构名称', '信息系统类别']] # 表头
# 按省份填充数据
for province, province_branches in branches_by_province.items():
# 添加省份标题行
data.append([f"{province}{len(province_branches)}", '', ''])
# 填充该省份的分支机构数据
for branch in province_branches:
data.append([branch.location, branch.name, branch.category])
# 添加空行分隔
data.append(['', '', ''])
# 创建表格
table = Table(data)
@@ -364,19 +452,59 @@ def export_branches_pdf(request):
style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), chinese_font), # 使用中文字体
('FONTSIZE', (0, 0), (-1, 0), 14),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('FONTNAME', (0, 1), (-1, -1), chinese_font), # 使用中文字体
('GRID', (0, 0), (-1, -1), 1, colors.black)
])
# 设置省份标题行的样式
current_row = 1
for province, province_branches in branches_by_province.items():
# 省份标题行
style.add('BACKGROUND', (0, current_row), (-1, current_row), colors.lightgrey)
style.add('FONTNAME', (0, current_row), (-1, current_row), chinese_font)
style.add('FONTSIZE', (0, current_row), (-1, current_row), 12)
style.add('TEXTCOLOR', (0, current_row), (-1, current_row), colors.black)
current_row += 1
# 分支机构数据行
for _ in province_branches:
style.add('BACKGROUND', (0, current_row), (-1, current_row), colors.beige)
current_row += 1
# 空行
current_row += 1
table.setStyle(style)
# 添加表格到文档
elements.append(table)
# 添加统计信息
elements.append(Spacer(1, 20))
# 省份统计
stats_style = ParagraphStyle(
'Stats',
parent=styles['Normal'],
fontName=chinese_font,
fontSize=12,
spaceAfter=10
)
stats_text = f"省份统计:总计 {len(branches)} 个分支机构,分布在 {len(branches_by_province)} 个省份"
elements.append(Paragraph(stats_text, stats_style))
# 类别统计
type_a_count = branches.filter(category='A型').count()
type_b_count = branches.filter(category='B型').count()
type_c_count = branches.filter(category='C型').count()
category_text = f"类别统计A型: {type_a_count}家 | B型: {type_b_count}家 | C型: {type_c_count}"
elements.append(Paragraph(category_text, stats_style))
# 构建PDF
doc.build(elements)
@@ -466,6 +594,176 @@ def contact_list(request):
return render(request, 'contact_list.html', context)
def export_contacts_xls(request):
# 获取筛选参数
branches_param = request.GET.get('branches')
category = request.GET.get('category')
contact_name = request.GET.get('contact_name')
# 构建查询条件
filters = Q()
# 分支机构筛选(支持多选)
if branches_param:
branch_ids = [bid.strip() for bid in branches_param.split(',') if bid.strip().isdigit()]
if branch_ids:
filters &= Q(branch_id__in=branch_ids)
# 联系人类别筛选
if category:
filters &= Q(category__contains=category)
# 联系人姓名筛选
if contact_name:
filters &= Q(name__icontains=contact_name)
# 获取筛选后的联系人数据
contacts = Contact.objects.filter(filters).order_by('branch__location', 'branch__name', 'name')
# 创建一个工作簿
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
workbook = Workbook()
worksheet = workbook.active
worksheet.title = "联系人信息"
# 添加表头
headers = ['分支机构', '联系人类别', '姓名', '电话', '邮箱']
for col_num, header in enumerate(headers, 1):
cell = worksheet.cell(row=1, column=col_num, value=header)
# 设置表头样式
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="4A90A4", end_color="4A90A4", fill_type="solid")
# 填充数据
row_num = 2
for contact in contacts:
worksheet.cell(row=row_num, column=1, value=contact.branch.name)
worksheet.cell(row=row_num, column=2, value=contact.category if contact.category else '')
worksheet.cell(row=row_num, column=3, value=contact.name if contact.name else '')
worksheet.cell(row=row_num, column=4, value=contact.phone if contact.phone else '')
worksheet.cell(row=row_num, column=5, value=contact.email if contact.email else '')
row_num += 1
# 设置列宽
worksheet.column_dimensions['A'].width = 30
worksheet.column_dimensions['B'].width = 20
worksheet.column_dimensions['C'].width = 12
worksheet.column_dimensions['D'].width = 15
worksheet.column_dimensions['E'].width = 30
# 创建响应对象
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename="contacts_info.xlsx"'
workbook.save(response)
return response
def export_contacts_pdf(request):
# 创建响应对象
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="contacts_info.pdf"'
# 注册中文字体
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# 尝试注册系统中文字体这里使用Windows系统自带的中文字体
try:
# 尝试注册微软雅黑字体
pdfmetrics.registerFont(TTFont('MSYaHei', 'C:/Windows/Fonts/msyh.ttc'))
chinese_font = 'MSYaHei'
except:
try:
# 如果微软雅黑不存在,尝试注册宋体
pdfmetrics.registerFont(TTFont('SimSun', 'C:/Windows/Fonts/simsun.ttc'))
chinese_font = 'SimSun'
except:
# 如果都找不到,使用默认字体(可能仍会有乱码)
chinese_font = 'Helvetica'
# 创建PDF文档
doc = SimpleDocTemplate(response, pagesize=letter)
elements = []
# 添加标题
styles = getSampleStyleSheet()
# 复制Title样式并修改字体
title_style = ParagraphStyle(
'ChineseTitle',
parent=styles['Title'],
fontName=chinese_font,
fontSize=18,
spaceAfter=30,
alignment=1 # 1表示居中
)
title = Paragraph("联系人信息", title_style)
elements.append(title)
# 获取筛选参数
branches_param = request.GET.get('branches')
category = request.GET.get('category')
contact_name = request.GET.get('contact_name')
# 构建查询条件
filters = Q()
# 分支机构筛选(支持多选)
if branches_param:
branch_ids = [bid.strip() for bid in branches_param.split(',') if bid.strip().isdigit()]
if branch_ids:
filters &= Q(branch_id__in=branch_ids)
# 联系人类别筛选
if category:
filters &= Q(category__contains=category)
# 联系人姓名筛选
if contact_name:
filters &= Q(name__icontains=contact_name)
# 获取筛选后的联系人
contacts = Contact.objects.filter(filters)
# 准备表格数据
data = [['分支机构', '分类', '姓名', '电话', '邮箱']] # 表头
for contact in contacts:
data.append([
contact.branch.name if contact.branch else '',
contact.category or '',
contact.name or '',
contact.phone or '',
contact.email or ''
])
# 创建表格
table = Table(data)
# 设置表格样式
style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), chinese_font), # 使用中文字体
('FONTSIZE', (0, 0), (-1, 0), 14),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('FONTNAME', (0, 1), (-1, -1), chinese_font), # 使用中文字体
('GRID', (0, 0), (-1, -1), 1, colors.black)
])
table.setStyle(style)
# 添加表格到文档
elements.append(table)
# 构建PDF
doc.build(elements)
return response
def video_terminal_list(request):
# 获取筛选参数
selected_branch = request.GET.get('branch', '')

382
requirements.txt Normal file
View File

@@ -0,0 +1,382 @@
absl-py==2.3.1
aiofiles==23.2.1
aiohttp==3.8.5
aiosignal==1.3.1
altgraph==0.17.4
annotated-types==0.6.0
anyio==3.7.1
appdirs==1.4.4
argcomplete==1.10.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
asgiref==3.7.2
astor==0.8.1
asttokens==3.0.0
astunparse==1.6.3
async-lru==2.0.5
async-timeout==4.0.3
attrdict==2.0.1
attrs==23.1.0
autobahn==24.4.2
Babel==2.12.1
bce-python-sdk==0.8.90
bcrypt==4.3.0
beautifulsoup4==4.8.0
binaryornot==0.4.4
bleach==6.2.0
blinker==1.6.2
briefcase==0.3.19
brotli==1.2.0
build==0.10.0
cachetools==5.3.1
certifi==2023.7.22
cffi==1.15.1
chardet==3.0.4
charset-normalizer==3.2.0
ci-info==0.3.0
click==8.1.6
clr-loader==0.2.6
cnocr==2.2.4
cnstd==1.2.3.4
colorama==0.4.6
coloredlogs==15.0.1
comm==0.2.2
comtypes==1.4.8
configobj==5.0.8
configparser==6.0.0
contourpy==1.1.1
cookiecutter==2.6.0
cryptography==41.0.3
cssselect==1.2.0
cssselect2==0.8.0
cssutils==2.7.1
cycler==0.11.0
Cython==3.0.2
dashscope==1.20.14
debugpy==1.8.13
decorator==5.1.1
defusedxml==0.7.1
diskcache==5.6.3
distro==1.9.0
Django==5.0.6
django-appconf==1.0.6
django-select2==8.2.1
django-unfold==0.36.0
djangorestframework==3.15.1
docker-pycreds==0.4.0
docx2pdf==0.1.8
docx2txt==0.8
EasyProcess==1.1
EbookLib==0.17.1
entrypoint2==1.1
et-xmlfile==1.1.0
etelemetry==0.3.0
exceptiongroup==1.1.3
executing==2.2.0
extract-msg==0.23.1
factory_boy==3.3.3
Faker==25.2.0
fastapi==0.103.2
fastjsonschema==2.21.1
filelock==3.12.4
fire==0.5.0
FLAML==2.1.1
Flask==2.3.3
flask-babel==3.1.0
flatbuffers==25.2.10
fonttools==4.61.1
fqdn==1.5.1
frontend==0.0.3
frozenlist==1.4.0
fsspec==2023.9.2
future==0.18.3
gast==0.6.0
gevent==23.7.0
gitdb==4.0.10
GitPython==3.1.32
google-pasta==0.2.0
greenlet==3.0.1
grpcio==1.74.0
h11==0.14.0
h5py==3.14.0
httpcore==0.18.0
httplib2==0.22.0
httpx==0.25.0
huggingface-hub==0.17.3
humanfriendly==10.0
hyperlink==21.0.0
idna==3.4
imageio==2.31.4
imagekitio==4.1.0
IMAPClient==2.1.0
imgaug==0.4.0
iniconfig==2.0.0
ipykernel==6.29.5
ipython==9.0.2
ipython_pygments_lexers==1.1.1
ipywidgets==8.1.5
isodate==0.6.1
isoduration==20.11.0
itsdangerous==2.1.2
jedi==0.19.2
Jinja2==3.1.2
jiter==0.12.0
joblib==1.5.1
json5==0.12.0
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter==1.1.1
jupyter-console==6.6.3
jupyter-events==0.12.0
jupyter-lsp==2.2.5
jupyter_client==8.6.3
jupyter_core==5.7.2
jupyter_server==2.15.0
jupyter_server_proxy==4.4.0
jupyter_server_terminals==0.5.3
jupyterlab==4.3.6
jupyterlab_pygments==0.3.0
jupyterlab_server==2.27.3
jupyterlab_vpython==3.1.8
jupyterlab_widgets==3.0.13
jwt==1.3.1
keras==3.11.1
keyboard==0.13.5
kiwisolver==1.4.5
lazy_loader==0.3
libclang==18.1.1
lightning-utilities==0.9.0
livekit-api==0.2.1
livekit-protocol==1.0.2
lmdb==1.4.1
loguru==0.7.2
looseversion==1.3.0
lxml==4.9.3
Markdown==3.8.2
markdown-it-py==3.0.0
MarkupSafe==2.1.3
matplotlib==3.8.0
matplotlib-inline==0.1.7
mdurl==0.1.2
mistune==3.1.3
ml_dtypes==0.5.3
MouseInfo==0.1.3
mpmath==1.3.0
mss==9.0.1
multidict==6.0.4
namex==0.1.0
nbclient==0.10.2
nbconvert==7.16.6
nbformat==5.10.4
nest-asyncio==1.6.0
networkx==3.1
nibabel==5.1.0
nipype==1.8.6
notebook==7.3.3
notebook_shim==0.2.4
Nuitka==2.7.13
numpy==1.26.0
olefile==0.46
onnx==1.14.1
onnxruntime==1.16.0
openai==2.14.0
opencv-contrib-python==4.6.0.66
opencv-python==4.11.0.86
openpyxl==3.1.2
opt-einsum==3.3.0
optree==0.17.0
ordered-set==4.1.0
outcome==1.2.0
overrides==7.7.0
packaging==23.1
paddle-bfloat==0.1.7
paddleocr==2.7.0.3
paddlepaddle==2.5.1
pandas==2.1.1
pandocfilters==1.5.1
paramiko==3.5.1
parso==0.8.4
pathtools==0.1.2
pdf2docx==0.5.6
pdf2image==1.17.0
pdfkit==1.0.0
pdfminer.six==20181108
pefile==2023.2.7
Pillow==10.0.1
platformdirs==3.10.0
playwright==1.40.0
pluggy==1.5.0
Polygon3==3.0.9.1
premailer==3.10.0
prometheus_client==0.21.1
prompt_toolkit==3.0.50
protobuf==3.20.2
prov==2.0.0
psutil==5.9.5
pure_eval==0.2.3
pyasn1==0.6.1
PyAudio==0.2.14
pyautogen==0.1.10
PyAutoGUI==0.9.54
pyclipper==1.3.0.post5
pycparser==2.21
pycryptodome==3.19.0
pydantic==2.10.4
pydantic_core==2.27.2
pydot==1.4.2
pydyf==0.12.1
pyee==11.0.1
PyGetWindow==0.0.9
Pygments==2.16.1
pyinstaller==6.9.0
pyinstaller-hooks-contrib==2024.7
PyJWT==2.10.1
PyMsgBox==1.0.9
PyMuPDF @ file:///C:/Users/dxzq/Downloads/PyMuPDF-1.20.2-cp311-cp311-win_amd64.whl#sha256=8969f3a8462352d8ddb3b679e87e85f84ae4fb49d2ef5d927563ceaca9a2aeaa
PyMuPDFb==1.23.3
PyNaCl==1.5.0
pynput==1.7.6
pyoxidizer==0.24.0
pyparsing==3.1.1
PyPDF2==3.0.1
pyperclip==1.8.2
pyphen==0.17.2
pyproject_hooks==1.0.0
PyQt5==5.15.11
PyQt5-Qt5==5.15.2
PyQt5-stubs==5.15.6.0
PyQt5_sip==12.17.0
PyQt6==6.10.0
PyQt6-Qt6==6.10.0
PyQt6_sip==13.10.2
pyreadline3==3.4.1
PyRect==0.2.0
pyscreenshot==3.1
PyScreeze==0.1.29
pyserial==3.5
PySide6==6.9.1
PySide6_Addons==6.9.1
PySide6_Essentials==6.9.1
pysnmp==7.1.15
PySocks==1.7.1
pytesseract==0.3.10
pytest==8.3.2
pytest-django==4.11.1
pytest-qt==4.5.0
python-dateutil==2.9.0.post0
python-docx==0.8.11
python-dotenv==1.0.0
python-json-logger==3.3.0
python-multipart==0.0.6
python-pptx==0.6.18
python-slugify==8.0.1
pythonnet==3.0.1
pytorch-lightning==2.0.9
pytweening==1.0.7
pytz==2023.3.post1
PyWavelets==1.4.1
pywin32==308
pywin32-ctypes==0.2.2
pywinauto==0.6.9
pywinpty==2.0.15
pyxnat==1.6
PyYAML==6.0.1
pyzmq==26.4.0
rapidfuzz==3.3.1
rarfile==4.1
rdflib==7.0.0
redis==7.0.1
referencing==0.36.2
reportlab==4.4.3
requests==2.31.0
requests-toolbelt==0.10.1
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rich==13.5.2
rpds-py==0.24.0
scapy==2.6.1
schedule==1.2.2
scikit-image==0.21.0
scikit-learn==1.7.1
scipy==1.11.2
seaborn==0.12.2
selenium==4.11.2
Send2Trash==1.8.3
sentry-sdk==1.31.0
setproctitle==1.3.2
shapely==2.0.1
shiboken6==6.9.1
simpervisor==1.0.0
simplejson==3.19.1
six==1.14.0
smmap==5.0.0
sniffio==1.3.0
sortedcontainers==2.4.0
soupsieve==2.5
SpeechRecognition==3.8.1
sqlparse==0.4.4
stack-data==0.6.3
starlette==0.27.0
sympy==1.12
tensorboard==2.19.0
tensorboard-data-server==0.7.2
tensorflow==2.19.0
tensorflow-io-gcs-filesystem==0.31.0
termcolor==2.3.0
terminado==0.18.1
text-unidecode==1.3
textract==1.6.3
threadpoolctl==3.6.0
tifffile==2023.9.26
tinycss2==1.5.1
tinyhtml5==2.0.0
toga==0.3.1
toga-core==0.4.6
toga-winforms==0.4.6
tomli_w==1.0.0
torch==2.0.1
torchmetrics==1.2.0
torchvision==0.15.2
tornado==6.4.2
tqdm==4.66.1
traitlets==5.14.3
traits==6.3.2
travertino==0.3.0
trio==0.22.2
trio-websocket==0.10.3
txaio==23.1.1
types-protobuf==5.29.1.20250403
typing_extensions==4.12.2
tzdata==2023.3
tzlocal==1.5.1
Unidecode==1.3.7
uri-template==1.3.0
urllib3==1.26.20
uvicorn==0.23.2
visualdl==2.5.3
vpython==7.6.5
wandb==0.15.11
wcwidth==0.2.13
weasyprint==67.0
webcolors==24.11.1
webdriver-manager==4.0.0
webencodings==0.5.1
websocket==0.2.1
websocket-client==1.8.0
Werkzeug==2.3.7
wget==3.2
widgetsnbextension==4.0.13
win32-setctime==1.1.0
wrapt==1.17.2
wsproto==1.2.0
xlrd==1.2.0
XlsxWriter==3.2.0
yarl==1.9.2
zai-sdk==0.0.4
zhipuai==2.1.5.20250106
zope.event==5.0
zope.interface==6.0
zopfli==0.4.0
zstandard==0.24.0