Compare commits

...

52 Commits
v0.5 ... master

Author SHA1 Message Date
Yutong LI f889efc8cd
Update code_quality.yml 2023-02-07 23:26:52 +08:00
Yutong LI 8bee3fca96
Update code_quality.yml 2023-02-07 23:17:49 +08:00
Yutong LI d813066e03
Update code_quality.yml 2023-02-07 23:12:41 +08:00
Yutong LI 483432b913
Create code_quality.yml 2023-02-07 23:09:51 +08:00
Yutong LI f8cb32e702
Create django.yml 2023-01-28 23:54:12 +08:00
raiots 8d710ac088 feat: add docs for install 2023-01-06 10:23:41 +08:00
raiots b0433783c8 feat: redirect to passed-in url when submit todo 2023-01-05 22:57:42 +08:00
raiots 97a5e22795 docs: update README.md 2022-08-15 10:15:58 +08:00
raiots 1936734006 Merge remote-tracking branch 'origin/master' 2022-07-26 14:04:05 +08:00
raiots 3b891ad229 docs init 2022-07-22 16:39:48 +08:00
raiots ca826cfb19 fix: when month change, the default page does not change the view according to the current 2022-07-21 11:47:52 +08:00
raiots 6103d632d0 feat: background at login page 2022-07-21 11:27:36 +08:00
raiots b87ea4c9bd fix: KeyError when user do not have todo 2022-04-25 18:56:13 +08:00
raiots fc4bbc748c add: Docker Update 2022-04-18 23:57:40 +08:00
raiots 6a46184fda add: Docker Support 2022-01-30 13:16:15 +08:00
raiots c6e965b38a add: filter for choose task in todo's admin page 2022-01-08 21:09:36 +08:00
raiots b0cdc33fb1 fix: when save the task with 0 sub_executor return key error 2022-01-07 11:29:04 +08:00
raiots 6495b8cf2b add: not only unfold but can also fold in tasklist 2022-01-05 20:41:25 +08:00
raiots 86bf9bd1c6 add: header change to prepare for new feat 2022-01-05 13:04:33 +08:00
raiots 1fa02689f8 fix: Not standard display data in index view 2022-01-05 12:54:29 +08:00
raiots 066dd333a8 add: pager for tasklist view to change year
fix: query bug which cause repeated results in tasklist
2022-01-04 22:56:49 +08:00
raiots 69af43c01c fix: the unfiltered by year in tasklist view 2022-01-04 17:15:16 +08:00
raiots 92971237f5 Merge remote-tracking branch 'origin/master' 2021-11-08 13:51:46 +08:00
raiots df9e174bd3 update: calclate the real workload and quality by quarter 2021-10-29 16:17:11 +08:00
Raiot 3047ba6ed5
Merge pull request #2 from raiots/add-license-1
Create LICENSE
2021-10-02 21:41:32 +08:00
Raiot ead6413fcc
Create LICENSE 2021-10-02 21:40:51 +08:00
Raiot 4f827bdee4
Update README.md 2021-09-12 00:35:03 +08:00
Raiot 7930bd4c89
Update README.md 2021-09-12 00:34:13 +08:00
raiots 3283478b42 update: new query method to calculate workload, the sub_executor_count field in database can be remove now. 2021-08-22 23:24:47 +08:00
raiots 81c2e817d1 Merge remote-tracking branch 'origin/master' 2021-08-18 21:46:09 +08:00
raiots 177ff0ea9a fix: about.html, name change
simpleUI 2021.4.1 --> 2021.8.1
add: unfold function on tasklist.html;
test raw sql query
2021-08-18 21:44:26 +08:00
raiots a194cb664d fix: about.html, name change
simpleUI 2021.4.1 --> 2021.8.1
add: unfold function on tasklist.html;
test raw sql query
2021-08-18 21:43:51 +08:00
Raiot 1f0f65dfb9
Update .gitattributes 2021-07-21 22:57:41 +08:00
Raiot a31135cfcc
Update .gitattributes 2021-07-21 22:56:26 +08:00
raiots 27f54562df fix: security vulnerabilities
Chart.js 2.9.4 --> 3.4.1
django 3.1.5 --> 3.1.12
2021-07-21 22:45:09 +08:00
raiots d7359e3ea8 add: chart of work load for each member in department & millions of bugs. 2021-05-05 00:48:03 +08:00
Raiot 3d08ffce71
Update .gitattributes 2021-04-13 14:44:00 +08:00
raiots 80f3f7f9d8 fix: fix some bugs; evaluate_factor editable in details 2021-04-10 16:26:44 +08:00
raiots a6022a3865 update: README.md 2021-03-31 12:44:33 +08:00
raiots 151fcaacb5 add: import-export 2021-03-27 21:35:41 +08:00
raiots 39ab683cd6 add: admin仅显示当前部门的年度任务/工作包 2021-03-26 16:32:39 +08:00
raiots 8b4d29a71d add: 承办人与协办人只显示本部门人员,Index中仅显示本部门统计,部门任务界面按编号+完成时间排序 2021-03-26 09:25:40 +08:00
raiots 42ce5e6fc8 add: some note & analyse table on index.html 2021-03-26 08:05:13 +08:00
raiots 004ba6a0a6 fix: fix typo & main executor dispear 2021-03-23 12:15:04 +08:00
raiots 4748f2b290 修复登入界面的bug 2021-03-19 15:01:10 +08:00
raiots 6ef6ecff5b [回滚]修复上一commit因为Pycharm批量重命名导致的Bug 2021-03-18 22:56:52 +08:00
raiots 4bfc8eb5cd 优化界面,增加About页面 2021-03-13 22:31:52 +08:00
raiots 58489517de 优化界面 2021-03-10 22:06:58 +08:00
raiots 7a73917e1c 完善可自定义的管理界面 2021-03-09 17:08:31 +08:00
raiots 15f0165ae4 关于序号错误的bug 2021-03-06 15:31:29 +08:00
raiots 15f80ce25d Fix Bugs
解决了工作包界面显示异常的bug
修改工作包对年度任务的多对多关系为外键
增加about页面
2021-03-06 14:31:15 +08:00
raiots 3f3f91f408 Fix Bugs
修复了年度任务中工作包显示异常的bug
2021-03-04 22:13:36 +08:00
65 changed files with 33077 additions and 16670 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
static/** linguist-vendored

20
.github/workflows/code_quality.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- master
- 'releases/*'
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@main
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}

30
.github/workflows/django.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Django CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: |
python manage.py test

5
.gitignore vendored
View File

@ -3,3 +3,8 @@
/packagesdir/ /packagesdir/
/db.sqlite3 /db.sqlite3
/identifier.sqlite /identifier.sqlite
migrations/
node_modules
.temp
.cache

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
# 从仓库拉取 带有 python 3.8 的 Linux 环境
FROM python:3.8
# 设置 python 环境变量
ENV PYTHONUNBUFFERED 1
# 创建 code 文件夹并将其设置为工作目录
RUN mkdir /code
WORKDIR /code
# 更新 pip
RUN pip install pip -U
# 将 requirements.txt 复制到容器的 code 目录
ADD requirements.txt /code/
# 安装库
RUN pip install -r requirements.txt -i https://mirrors.ustc.edu.cn/pypi/web/simple
# 将当前目录复制到容器的 code 目录
ADD . /code/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Raiot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -9,7 +9,7 @@ Author [面壁的雨](https://raiot.me)
到时候会出一个文档的(或许 到时候会出一个文档的(或许
from 2020-7-13 to 2021-2-2 from 2020-7-13 to 2021-3-31
鬼知道为什么会用这么长时间 鬼知道为什么会用这么长时间
@ -18,3 +18,27 @@ from 2020-7-13 to 2021-2-2
#### 注意! #### 注意!
* 此版本仅为beta版本请勿在生产环境使用 * 此版本仅为beta版本请勿在生产环境使用
### 介绍
这是一个由Django开发的任务分发系统
![QQ20210912003100.png](https://images.everains.com/images/2021/09/12/QQ20210912003100.png)
![QQ20210912003117.png](https://images.everains.com/images/2021/09/12/QQ20210912003117.png)
![QQ20210912003139.png](https://images.everains.com/images/2021/09/12/QQ20210912003139.png)
![QQ20210912003237.png](https://images.everains.com/images/2021/09/12/QQ20210912003237.png)
#### feature
* 员工可以看到自己负责的承办任务和协办任务
* 部门全年任务时间表
* 高定制度的后台管理页面
### Contributors
JetBrains OS License
TasksManager has been developed with PyCharm IDE under JetBrains Open Source License.

View File

@ -10,6 +10,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/ https://docs.djangoproject.com/en/3.1/ref/settings/
""" """
import os import os
import time
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -25,7 +26,7 @@ SECRET_KEY = '&!38pk#dv=r!_c(+b&oegc0m(ndzoue+ez*7kvjv2uubuqootp'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['192.168.31.198', '127.0.0.1', '172.20.22.40', '110.42.209.79']
# Application definition # Application definition
@ -38,6 +39,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'import_export',
'apps.users', 'apps.users',
'apps.tasks', 'apps.tasks',
] ]
@ -134,7 +136,10 @@ LOGIN_REDIRECT_URL = '/'
# 自定义用户模型 # 自定义用户模型
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = 'users.User'
# 它确定库是否会在数据导入中使用数据库事务,以确保安全。
IMPORT_EXPORT_USE_TRANSACTIONS = True
SITE_NAME = '任务管理工具'
# SimpleUI 配置 # SimpleUI 配置
# 离线模式 # 离线模式
@ -145,8 +150,89 @@ SIMPLEUI_HOME_INFO = False
SIMPLEUI_HOME_QUICK = True SIMPLEUI_HOME_QUICK = True
SIMPLEUI_HOME_ACTION = True SIMPLEUI_HOME_ACTION = True
SIMPLEUI_ANALYSIS = False SIMPLEUI_ANALYSIS = False
SIMPLEUI_HOME_TITLE = '任务管理系统' SIMPLEUI_HOME_TITLE = SITE_NAME
# SIMPLEUI_HOME_PAGE = 'https://www.baidu.com' # 可用于嵌入其他链接,这里可以直接方便的嵌入报表链接 # SIMPLEUI_HOME_PAGE = 'https://www.baidu.com' # 可用于嵌入其他链接,这里可以直接方便的嵌入报表链接
SIMPLEUI_HOME_ICON = 'el el-icon-platform-eleme' SIMPLEUI_HOME_ICON = 'el el-icon-platform-eleme'
# ICON 支持element-ui和fontawesome egfa fa-user # ICON 支持element-ui和fontawesome egfa fa-user
# https://zhuanlan.zhihu.com/p/113447102 # https://zhuanlan.zhihu.com/p/113447102
# 指定simpleui默认的主题,指定一个文件名相对路径就从simpleui的theme目录读取
SIMPLEUI_DEFAULT_THEME = 'ant.design.css'
SIMPLEUI_CONFIG = {
'system_keep': False,
'menu_display': ['任务管理', '系统配置', '关于'], # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [{
'name': '任务管理',
'icon': 'fas fa-code',
'models': [{
'app': 'tasks',
'name': '年度任务',
'url': 'tasks/task'
}, {
'name': '工作包',
'url': 'tasks/todo?o=3'
}]
}, {
'app': 'admin',
'name': '系统配置',
'icon': 'fas fa-user-shield',
'models': [{
'name': '用户',
'icon': 'fa fa-user',
'url': 'users/user',
}, {
'name': '部门',
'url': 'users/department'
}, {
'name': '任务属性',
'url': 'users/taskproperty',
}, {
'name': '权限组',
'url': 'users/mygroup',
}, {
'name': '评价等级定义',
'url': 'users/qualitymark',
}, {
'name': '评价等级考核系数',
'url': 'users/markvalue'
}]
}, {
# 自2021.02.01+ 支持多级菜单models 为子菜单名
'name': '多级菜单测试',
'icon': 'fa fa-file',
# 二级菜单
'models': [{
'name': 'Baidu',
'icon': 'far fa-surprise',
# 第三级菜单
'models': [
{
'name': '爱奇艺',
'url': 'https://www.iqiyi.com/dianshiju/'
# 第四级就不支持了element只支持了3级
}, {
'name': '百度问答',
'icon': 'far fa-surprise',
'url': 'https://zhidao.baidu.com/'
}
]
}, {
'name': '内网穿透',
'url': 'https://www.wezoz.com',
'icon': 'fab fa-github'
}]
}, {
'name': '动态菜单测试',
'icon': 'fa fa-desktop',
'models': [{
'name': time.time(),
'url': 'http://baidu.com',
'icon': 'far fa-surprise'
}]
}, {
'name': '关于',
'url': '/about'
}]
}

View File

@ -15,12 +15,13 @@ Including another URLconf
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from TasksManager.settings import SITE_NAME
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', include('apps.tasks.urls', namespace='tasks')), path('', include('apps.tasks.urls', namespace='tasks')),
] ]
admin.site.site_header = '任务进度管理系统' admin.site.site_header = SITE_NAME
admin.site.site_title = '任务进度管理系统' admin.site.site_title = SITE_NAME
admin.site.index_title = u'任务进度管理系统' admin.site.index_title = SITE_NAME

View File

@ -1,13 +1,39 @@
from datetime import datetime
import re import re
from django.contrib import admin from django.contrib import admin
from django.http import JsonResponse
from django.utils.html import format_html from django.utils.html import format_html
from import_export.admin import ImportExportModelAdmin
from import_export.formats import base_formats
from . import models from . import models
from apps.users.models import TaskProperty from apps.users.models import TaskProperty, User
from .resources import TodoResources, TaskResources
class TaskAdmin(admin.ModelAdmin): class TodoInline(admin.StackedInline):
# 在Inline中同样筛选仅本部门的承办人、协办人
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'related_task':
kwargs["queryset"] = models.Task.objects.filter(department=request.user.department)
elif db_field.name == 'main_executor':
kwargs["queryset"] = User.objects.filter(department=request.user.department)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'sub_executor':
kwargs["queryset"] = User.objects.filter(department=request.user.department)
return super().formfield_for_manytomany(db_field, request, **kwargs)
model = models.Todo
extra = 0
# classes = ['collapse']
#TODO 选择年度任务时排序 https://www.codenong.com/40740869/
class TaskAdmin(ImportExportModelAdmin):
resource_class = TaskResources
# def formfield_for_manytomany(self, db_field, request, **kwargs): # def formfield_for_manytomany(self, db_field, request, **kwargs):
# if db_field.name == "related_task": # if db_field.name == "related_task":
@ -20,14 +46,36 @@ class TaskAdmin(admin.ModelAdmin):
# pass # pass
# # kwargs["queryset"] = models.Task.objects.get(id=2).related_task # # kwargs["queryset"] = models.Task.objects.get(id=2).related_task
# 所属单位默认为访问用户的部门
def get_changeform_initial_data(self, request): def get_changeform_initial_data(self, request):
return {'department': request.user.department} return {'department': request.user.department}
# 年度任务编辑界面仅显示本部门的任务属性
def formfield_for_foreignkey(self, db_field, request, **kwargs): def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'task_property': if db_field.name == 'task_property':
kwargs["queryset"] = TaskProperty.objects.filter(own_department=request.user.department) kwargs["queryset"] = TaskProperty.objects.filter(own_department=request.user.department)
return super().formfield_for_foreignkey(db_field, request, **kwargs) return super().formfield_for_foreignkey(db_field, request, **kwargs)
# 仅显示当前部门的年度任务,除非为超管
def get_queryset(self, request):
qs = super(TaskAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(department=request.user.department)
def save_model(self, request, obj, form, change):
mvDict = dict(request.POST)
# 解决当工作包协办人数均为0时报错
# print(len(mvDict['related_task-0-sub_executor']))
try:
obj.related_task__sub_executor_count = int(len(mvDict['related_task-0-sub_executor']))
except:
obj.related_task__sub_executor_count = 0
super().save_model(request, obj, form, change)
list_display = ( list_display = (
'task_property', 'task_id', 'task_topic', 'task_origin', 'aim_value', 'deadline', 'duty_group', 'principal', 'task_property', 'task_id', 'task_topic', 'task_origin', 'aim_value', 'deadline', 'duty_group', 'principal',
'leader', 'task_note', 'leader', 'task_note',
@ -38,60 +86,291 @@ class TaskAdmin(admin.ModelAdmin):
'fields': ( 'fields': (
('task_property', 'task_id', 'task_topic', 'task_origin', 'aim_value', 'deadline', 'duty_group', ('task_property', 'task_id', 'task_topic', 'task_origin', 'aim_value', 'deadline', 'duty_group',
'principal', 'leader'), 'principal', 'leader'),
'task_note', 'related_task', 'department'), 'task_note', 'department'),
}), }),
) )
inlines = [TodoInline]
raw_id_fields = ("principal", "leader",) raw_id_fields = ("principal", "leader",)
autocomplete_fields = ('related_task',) list_display_links = ('task_topic',)
search_fields = ('related_task',) # autocomplete_fields = ('related_task',)
# search_fields = ('related_task',)
# 导入导出功能限制
def get_export_formats(self): # 该方法是限制格式为XLS
formats = (
base_formats.XLS,
)
return [f for f in formats if f().can_export()]
def has_import_permission(self, request): # 这是隐藏导入按钮,如果隐藏其他按钮也可以这样操作,
if request.user.is_superuser:
return True
else:
return False
class TodoAdmin(admin.ModelAdmin): # class TodoAdmin(admin.ModelAdmin):
def sub_executor(self, obj): class TodoAdmin(ImportExportModelAdmin):
return ', '.join([a.real_name for a in obj.sub_executor.all()]) resource_class = TodoResources
fieldsets = ( # 工作包页面仅显示所属本部门的年度任务、承办人、协办人
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'related_task':
kwargs["queryset"] = models.Task.objects.filter(department=request.user.department)
elif db_field.name == 'main_executor':
kwargs["queryset"] = User.objects.filter(department=request.user.department)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'sub_executor':
kwargs["queryset"] = User.objects.filter(department=request.user.department)
return super().formfield_for_manytomany(db_field, request, **kwargs)
# 仅显示当前部门的工作包,除非为超管
def get_queryset(self, request):
qs = super(TodoAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(related_task__department=request.user.department)
fieldsets = [
(None, { (None, {
'fields': ( 'fields': [
'todo_topic', 'todo_note', 'deadline', 'duty_group', 'main_executor', 'sub_executor', 'predict_work', 'related_task', 'todo_topic', 'todo_note', 'deadline', 'duty_group', 'main_executor', 'sub_executor',
'evaluate_factor', 'sub_executor_count', 'predict_work', 'evaluate_factor', 'real_work'
) ],
'description': ''
}), }),
('完成情况', {
'fields': ( (None, {
'real_work', 'complete_note', 'quality_mark', 'maturity' 'fields': [],
) }),
}) ]
)
list_display = ( list_display = (
'todo_topic', 'todo_topic',
'deadline', 'deadline',
'todo_note',
'task_id', 'task_id',
'lined_task',
'task_origin', 'task_origin',
'duty_department', 'lined_task',
# 'duty_department',
'duty_group', 'duty_group',
'main_executor', 'main_executor',
# 'sub_executor', 'list_sub_executor',
'predict_work',
'evaluate_factor',
'maturity',
'real_work',
'complete_note',
'quality_mark',
) )
list_editable = ['quality_mark']
list_filter = ('deadline',) list_filter = ('deadline',)
list_display_links = ('todo_topic', 'deadline', ) list_display_links = ('todo_topic', 'deadline', )
date_hierarchy = 'deadline' date_hierarchy = 'deadline'
list_per_page = 20 list_per_page = 70 # 目的是取消自动分页好像有bug
raw_id_fields = ("main_executor", "sub_executor") # raw_id_fields = ("sub_executor",)
search_fields = ('todo_topic',) search_fields = ('todo_topic',)
# ordering = ('task_id',) ordering = ('related_task', )
readonly_fields = ["attachment"]
def approval_state(self, obj): def approval_state(self, obj):
return format_html('<span style="color:{};">{}</span>', 'green', obj.approval) return format_html('<span style="color:{};">{}</span>', 'green', obj.approval)
def task_id(self, obj): def task_id(self, obj):
return obj.task_id return obj.task_id
def lined_task(self, obj): task_id.admin_order_field = 'related_task__task_id'
return obj.lined_task
task_id.admin_order_field = 'task__task_id'
task_id.short_description = '任务编号' task_id.short_description = '任务编号'
lined_task.admin_order_field = 'task__task_topic'
def task_origin(self, obj):
return obj.task_origin
task_origin.short_description = '任务来源'
def duty_department(self, obj):
return obj.duty_group
duty_department.short_description = '责任部门'
def lined_task(self, obj):
return obj.related_task
lined_task.short_description = '任务名称' lined_task.short_description = '任务名称'
# 导入导出功能限制
def get_export_formats(self): # 该方法是限制格式为XLS
formats = (
base_formats.XLS,
)
return [f for f in formats if f().can_export()]
def has_import_permission(self, request): # 这是隐藏导入按钮,如果隐藏其他按钮也可以这样操作,
if request.user.is_superuser:
return True
else:
return False
# 对工作包页面选择其所属的年度任务中,对年度任务进行筛选。条件为:年度任务的完成时间不早于今年或年度任务中有工作包的完成时间晚于今年
def get_form(self, request, obj=None, **kwargs):
form = super(TodoAdmin, self).get_form(request, obj, **kwargs)
query = models.Task.objects.filter(
department=request.user.department, deadline__year__gte=datetime.now().strftime('%Y')).order_by('task_id')\
| models.Task.objects.filter(
department=request.user.department, related_task__deadline__year__gte=datetime.now().strftime('%Y'))\
.order_by('task_id')
form.base_fields['related_task'].queryset = query.distinct()
return form
# def save_model(self, request, obj, form, change):
# # 这一行代码写了一个晚上呜呜! 解决了当保存时,无法从未保存的数据中获取协办人数的问题!
# mvDict = dict(request.POST)
# dicts = request.POST
# print(dicts)
# for key, values in dicts:
# print(key, values)
# obj.user = request.user
# obj.sub_executor_count = int(len(mvDict['sub_executor']))
# super().save_model(request, obj, form, change)
# 增加批量操作按钮
actions = ['bulk_action']
def bulk_action(self, request, queryset):
post = request.POST
# 这里获取到数据后,可以做些业务处理
# post中的_action 是方法名
# post中 _selected 是选中的数据,逗号分割
if not post.get('_selected'):
return JsonResponse(data={
'status': 'error',
'msg': '请先选中数据!'
})
else:
return JsonResponse(data={
'status': 'success',
'msg': '处理成功!'
})
# 显示的文本与django admin一致
bulk_action.short_description = '批量操作'
# icon参考element-ui icon与https://fontawesome.com
bulk_action.icon = 'el-icon-files'
# 指定element-ui的按钮类型参考https://element.eleme.cn/#/zh-CN/component/button
bulk_action.type = 'warning'
# 给按钮追加自定义的颜色
bulk_action.style = 'color:white;'
# 指定为弹出层,这个参数最关键
bulk_action.layer = {
# 弹出层中的输入框配置
# 这里指定对话框的标题
'title': '弹出层输入框',
# 提示信息
'tips': '这个弹出对话框是需要在admin中进行定义数据新增编辑等功能需要自己来实现。',
# 确认按钮显示文本
'confirm_button': '确认提交',
# 取消按钮显示文本
'cancel_button': '取消',
# 弹出层对话框的宽度默认50%
'width': '40%',
# 表单中 label的宽度对应element-ui的 label-width默认80px
'labelWidth': "80px",
'params': [{
# 这里的type 对应el-input的原生input属性默认为input
'type': 'input',
# key 对应post参数中的key
'key': 'name',
# 显示的文本
'label': '名称',
# 为空校验默认为False
'require': True
}, {
'type': 'select',
'key': 'type',
'label': '类型',
'width': '200px',
# size对应elementui的size取值为medium / small / mini
'size': 'small',
# value字段可以指定默认值
'value': '0',
'options': [{
'key': '0',
'label': '收入'
}, {
'key': '1',
'label': '支出'
}]
}, {
'type': 'number',
'key': 'money',
'label': '金额',
# 设置默认值
'value': 1000
}, {
'type': 'date',
'key': 'date',
'label': '日期',
}, {
'type': 'datetime',
'key': 'datetime',
'label': '时间',
}, {
'type': 'rate',
'key': 'star',
'label': '评价等级'
}, {
'type': 'color',
'key': 'color',
'label': '颜色'
}, {
'type': 'slider',
'key': 'slider',
'label': '滑块'
}, {
'type': 'switch',
'key': 'switch',
'label': 'switch开关'
}, {
'type': 'input_number',
'key': 'input_number',
'label': 'input number'
}, {
'type': 'checkbox',
'key': 'checkbox',
# 必须指定默认值
'value': [],
'label': '复选框',
'options': [{
'key': '0',
'label': '收入'
}, {
'key': '1',
'label': '支出'
}, {
'key': '2',
'label': '收益'
}]
}, {
'type': 'radio',
'key': 'radio',
'label': '单选框',
'options': [{
'key': '0',
'label': '收入'
}, {
'key': '1',
'label': '支出'
}, {
'key': '2',
'label': '收益'
}]
}]
}
admin.site.register(models.Task, TaskAdmin) admin.site.register(models.Task, TaskAdmin)
admin.site.register(models.Todo, TodoAdmin) admin.site.register(models.Todo, TodoAdmin)

View File

@ -1,14 +1,38 @@
from django import forms from django import forms
from .models import Todo from .models import Todo
class LoginForm(forms.Form): class LoginForm(forms.Form):
username = forms.CharField(error_messages={'required': '用户名不能为空'}) username = forms.CharField(error_messages={'required': '用户名不能为空'})
password = forms.CharField() password = forms.CharField()
remember = forms.BooleanField(required=False) remember = forms.BooleanField(required=False)
# TODO 数据不可为空
class TodoForm(forms.ModelForm): class TodoForm(forms.ModelForm):
required_css_class = 'required'
# (confused by Form & ModelForm https://stackoverflow.com/questions/2303268/djangos-forms-form-vs-forms-modelform)
# maturity = forms.ChoiceField(widget=forms.Select(attrs={'class': 'form-control'}), choices=(
# ('0%', '0%'),
# ('10%', '10%'),
# ('50%', '50%'),
# ('90%', '90%'),
# ('100%', '100%')
# ))
# real_work = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'}))
# sub_executor = forms.MultipleChoiceField(widget=forms.SelectMultiple(attrs={'class': 'form-control'}))
class Meta: class Meta:
model = Todo model = Todo
fields = ['maturity', 'complete_note'] fields = ['maturity', 'real_work', 'sub_executor', 'evaluate_factor', 'complete_note']
labels ={'text': ''} widgets = {'complete_note': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
widgets = {'row': '3'} # 'evaluate_factor': forms.NumberInput(attrs={'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
super(TodoForm, self).__init__(*args, **kwargs)
# self.fields['sub_executor'].widget.attrs['class'] = 'form-control'
fields = ['maturity', 'real_work', 'sub_executor', 'evaluate_factor', 'complete_note']
for i in fields:
self.fields[i].widget.attrs['class'] = 'form-control'

View File

@ -1,50 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 13:31
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Task',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_topic', models.CharField(max_length=50, verbose_name='任务名称')),
('task_id', models.CharField(max_length=50, unique=True, verbose_name='任务编号')),
('task_note', models.CharField(max_length=100, verbose_name='任务说明')),
('task_origin', models.CharField(max_length=150, verbose_name='任务来源')),
('aim_value', models.CharField(max_length=50, verbose_name='目标值')),
('deadline', models.DateField(verbose_name='完成时间')),
],
options={
'verbose_name': '年度任务',
'verbose_name_plural': '年度任务',
'ordering': ['deadline'],
},
),
migrations.CreateModel(
name='Todo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('todo_topic', models.CharField(max_length=50, verbose_name='工作事项')),
('todo_note', models.CharField(blank=True, max_length=100, verbose_name='工作要求及交付物')),
('predict_work', models.DecimalField(decimal_places=1, max_digits=5, verbose_name='预计工作量')),
('evaluate_factor', models.DecimalField(decimal_places=1, max_digits=5, verbose_name='折算系数')),
('maturity', models.CharField(blank=True, choices=[('0%', '0%'), ('10%', '10%'), ('20%', '20%'), ('30%', '30%'), ('40%', '40%'), ('50%', '50%'), ('60%', '60%'), ('70%', '70%'), ('80%', '80%'), ('90%', '90%'), ('100%', '100%')], default='0%', max_length=5, verbose_name='成熟度')),
('real_work', models.DecimalField(blank=True, decimal_places=1, max_digits=5, null=True, verbose_name='实际工作量')),
('complete_note', models.TextField(blank=True, max_length=150, verbose_name='完成情况说明')),
('deadline', models.DateField(verbose_name='完成时间')),
],
options={
'verbose_name': '每月工作事项',
'verbose_name_plural': '每月工作事项',
'ordering': ['deadline'],
},
),
]

View File

@ -1,69 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 13:31
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('tasks', '0001_initial'),
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='todo',
name='duty_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.mygroup', verbose_name='承办单位'),
),
migrations.AddField(
model_name='todo',
name='main_executor',
field=models.ManyToManyField(related_name='main_executor', to=settings.AUTH_USER_MODEL, verbose_name='承/督办人'),
),
migrations.AddField(
model_name='todo',
name='quality_mark',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='users.qualitymark', verbose_name='质量评价'),
),
migrations.AddField(
model_name='todo',
name='sub_executor',
field=models.ManyToManyField(blank=True, default='', related_name='sub_executor', to=settings.AUTH_USER_MODEL, verbose_name='协办人'),
),
migrations.AddField(
model_name='task',
name='department',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.department', verbose_name='所属单位'),
),
migrations.AddField(
model_name='task',
name='duty_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.mygroup', verbose_name='责任单位'),
),
migrations.AddField(
model_name='task',
name='leader',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leader', to=settings.AUTH_USER_MODEL, verbose_name='主管领导'),
),
migrations.AddField(
model_name='task',
name='principal',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='principal', to=settings.AUTH_USER_MODEL, verbose_name='负责人'),
),
migrations.AddField(
model_name='task',
name='related_task',
field=models.ManyToManyField(blank=True, to='tasks.Todo', verbose_name='任务节点'),
),
migrations.AddField(
model_name='task',
name='task_property',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.taskproperty', verbose_name='任务属性'),
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 13:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20210129_1332'),
('tasks', '0002_auto_20210129_1331'),
]
operations = [
migrations.AlterField(
model_name='task',
name='department',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='department', to='users.department', verbose_name='所属单位'),
),
migrations.AlterField(
model_name='task',
name='duty_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='duty_group', to='users.department', verbose_name='责任单位'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 14:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0004_auto_20210129_1434'),
('tasks', '0003_auto_20210129_1335'),
]
operations = [
migrations.AlterField(
model_name='todo',
name='duty_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.department', verbose_name='承办单位'),
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-30 20:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0005_auto_20210130_2021'),
('tasks', '0004_auto_20210129_1434'),
]
operations = [
migrations.AlterField(
model_name='task',
name='department',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='department', to='users.department', verbose_name='所属单位'),
),
migrations.AlterField(
model_name='task',
name='duty_group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='duty_group', to='users.department', verbose_name='责任单位'),
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-01 19:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('tasks', '0005_auto_20210130_2021'),
]
operations = [
migrations.RemoveField(
model_name='todo',
name='main_executor',
),
migrations.AddField(
model_name='todo',
name='main_executor',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='main_executor', to=settings.AUTH_USER_MODEL, verbose_name='承/督办人'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-01 19:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0006_auto_20210201_1900'),
('tasks', '0006_auto_20210201_1900'),
]
operations = [
migrations.AlterField(
model_name='todo',
name='quality_mark',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.qualitymark', verbose_name='质量评价'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-01 22:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tasks', '0007_auto_20210201_1901'),
]
operations = [
migrations.AlterModelOptions(
name='todo',
options={'ordering': ['deadline'], 'verbose_name': '工作包', 'verbose_name_plural': '工作包'},
),
]

View File

@ -1,4 +1,8 @@
import time
from django.db import models from django.db import models
from django.http import request
from apps.users.models import User, MyGroup, QualityMark, TaskProperty, Department from apps.users.models import User, MyGroup, QualityMark, TaskProperty, Department
@ -12,35 +16,34 @@ class Todo(models.Model):
blank=True, blank=True,
max_length=100 max_length=100
) )
deadline = models.DateField(verbose_name='完成时间')
# related_task = models.ManyToManyField(Task, verbose_name="关联的主任务") # related_task = models.ManyToManyField(Task, verbose_name="关联的主任务")
duty_group = models.ForeignKey('users.Department', on_delete=models.CASCADE, verbose_name='承办单位') duty_group = models.ForeignKey('users.Department', on_delete=models.CASCADE, verbose_name='承办单位')
main_executor = models.ForeignKey(User, related_name='main_executor', on_delete=models.SET_NULL, verbose_name='承/督办人', null=True) main_executor = models.ForeignKey(User, related_name='main_executor', on_delete=models.CASCADE,
sub_executor = models.ManyToManyField(User, related_name='sub_executor', verbose_name='协办人', default='', blank=True) verbose_name='承/督办人', blank=True, null=True)
predict_work = models.DecimalField('预计工作量', max_digits=5, decimal_places=1) sub_executor = models.ManyToManyField(User, related_name='sub_executor', verbose_name='协办人', blank=True)
evaluate_factor = models.DecimalField('折算系数', max_digits=5, decimal_places=1) sub_executor_count = models.CharField('协办人数', max_length=32)
related_task = models.ForeignKey('Task', related_name='related_task', on_delete=models.CASCADE, verbose_name='年度任务')
predict_work = models.DecimalField('预计工作量', default=0, max_digits=5, decimal_places=1, blank=False)
evaluate_factor = models.DecimalField('折算系数', max_digits=5, decimal_places=1, blank=True, default='1') #Todo 小数点后2位
maturity = models.CharField( maturity = models.CharField(
verbose_name='成熟度', verbose_name='成熟度',
max_length=5, max_length=5,
choices=( choices=(
('0%', '0%'), ('0%', '0%'),
('10%', '10%'), ('10%', '10%'),
('20%', '20%'),
('30%', '30%'),
('40%', '40%'),
('50%', '50%'), ('50%', '50%'),
('60%', '60%'),
('70%', '70%'),
('80%', '80%'),
('90%', '90%'), ('90%', '90%'),
('100%', '100%') ('100%', '100%')
), ),
blank=True, blank=True,
default='0%', default='0%',
) )
real_work = models.DecimalField('实际工作量', max_digits=5, decimal_places=1, blank=True, null=True) real_work = models.DecimalField('实际工作量', default=0, max_digits=5, decimal_places=1, blank=False)
complete_note = models.TextField('完成情况说明', max_length=150, blank=True) complete_note = models.TextField('完成情况说明', max_length=500, blank=True)
quality_mark = models.ForeignKey('users.QualityMark', on_delete=models.SET_NULL, blank=True, null=True, verbose_name='质量评价') quality_mark = models.ForeignKey('users.QualityMark', on_delete=models.SET_NULL, blank=True, null=True,
deadline = models.DateField(verbose_name='完成时间') verbose_name='质量评价')
attachment = models.FileField('交付物查看', blank=True)
def __str__(self): def __str__(self):
date = str(self.deadline) date = str(self.deadline)
@ -52,55 +55,74 @@ class Todo(models.Model):
verbose_name = '工作包' verbose_name = '工作包'
verbose_name_plural = '工作包' verbose_name_plural = '工作包'
@property # def save(self, *args, **kwargs):
def lined_task(self): # super(Todo, self).save(*args, **kwargs)
lined_task = Task.objects.filter(related_task=self) # print(request.HttpRequest)
for task in lined_task: # # 直接保存报错 needs to have a value for field "id" before this many-to-many relationship can be used.
return task.task_topic # # self.sub_executor_count = int(self.sub_executor.count())
# TODO 不知道有没有不用for循环直接查的 # # Todo.objects.update(sub_executor_count=self.sub_executor.count())
@property @property
def task_id(self): def task_id(self):
tasks = Task.objects.filter(related_task=self) return self.related_task.task_id
for task in tasks:
return task.task_id
@property
def task_origin(self): def task_origin(self):
tasks = Task.objects.filter(related_task=self) return self.related_task.task_origin
for task in tasks:
return task.task_origin @property
def duty_department(self): def duty_department(self):
tasks = Task.objects.filter(related_task=self) return self.related_task.duty_group
for task in tasks:
return task.duty_group
@property @property
def last_month_list(self): def last_month_list(self):
return self.deadline return self.deadline
@property @property
def get_tatal_num(self): def get_total_num(self):
return self.objects.all().count() return self.objects.all().count()
@property
def points(self):
return int(self.predict_work * self.evaluate_factor)
@property
def main_workload(self):
return int(self.predict_work) * int(self.evaluate_factor)
@property
def sub_workload(self):
return self.predict_work * (1-int(self.evaluate_factor))/self.sub_executor.count
@classmethod
def sub_member(cls):
return cls.sub_executor.count
def list_sub_executor(self):
return ', '.join([a.real_name for a in self.sub_executor.all()])
list_sub_executor.short_description = '协办人'
class Task(models.Model): class Task(models.Model):
task_topic = models.CharField( task_topic = models.CharField(
verbose_name='任务名称', verbose_name='任务名称',
max_length=50 max_length=50
) )
task_id = models.CharField(max_length=50, unique=True, verbose_name='任务编号') task_id = models.CharField(max_length=50, unique=True, verbose_name='编号')
task_note = models.CharField( task_note = models.CharField(
verbose_name='任务说明', verbose_name='任务说明',
max_length=100 max_length=100,
blank=True
) )
task_origin = models.CharField(max_length=150, verbose_name='任务来源') task_origin = models.CharField(max_length=150, verbose_name='任务来源', blank=True)
task_property = models.ForeignKey('users.TaskProperty', on_delete=models.CASCADE, verbose_name='任务属性') task_property = models.ForeignKey('users.TaskProperty', on_delete=models.CASCADE, verbose_name='任务属性')
related_task = models.ManyToManyField(Todo, verbose_name='任务节点', blank=True) department = models.ForeignKey('users.Department', related_name='department', on_delete=models.SET_NULL, blank=True,
department = models.ForeignKey('users.Department', related_name='department', on_delete=models.SET_NULL, blank=True, null=True, verbose_name='所属单位') null=True, verbose_name='所属单位')
duty_group = models.ForeignKey('users.Department', related_name='duty_group', on_delete=models.SET_NULL, blank=True, null=True, verbose_name='责任单位') duty_group = models.ForeignKey('users.Department', related_name='duty_group', on_delete=models.SET_NULL, blank=True,
principal = models.ForeignKey(User, related_name='principal', verbose_name='负责人', on_delete=models.CASCADE) null=True, verbose_name='责任单位')
leader = models.ForeignKey(User, related_name='leader', verbose_name='主管领导', on_delete=models.CASCADE) principal = models.ForeignKey(User, related_name='principal', verbose_name='负责人', on_delete=models.CASCADE, blank=True, null=True)
aim_value = models.CharField(max_length=50, verbose_name='目标值') leader = models.ForeignKey(User, related_name='leader', verbose_name='主管领导', on_delete=models.CASCADE, blank=True, null=True)
aim_value = models.CharField(max_length=50, verbose_name='目标值', blank=True)
# start_date = models.DateField(verbose_name='起始日期') # start_date = models.DateField(verbose_name='起始日期')
deadline = models.DateField(verbose_name='完成时间') deadline = models.DateField(verbose_name='完成时间')

40
apps/tasks/my_query.py Normal file
View File

@ -0,0 +1,40 @@
from django.db import connection
def my_annotate():
query1 = '''
CREATE TEMPORARY TABLE work_cal AS
SELECT tasks_todo_sub_executor.*, tasks_todo.*
FROM tasks_todo_sub_executor
INNER JOIN tasks_todo
ON tasks_todo_sub_executor.todo_id = tasks_todo.id
ORDER BY user_id;
'''
query2 = '''
-- 计算每个工作包协办人数量并插入临时表
UPDATE work_cal SET sub_executor_count = (
SELECT COUNT(*) FROM tasks_todo_sub_executor
WHERE work_cal.todo_id = tasks_todo_sub_executor.todo_id);
'''
query3 = '''
SELECT exe_id, SUM(total_pre_work), SUM(total_real_work) FROM
(
-- 承办任务
SELECT main_executor_id AS exe_id, SUM(predict_work * evaluate_factor) AS total_pre_work,
SUM(real_work * tasks_todo.evaluate_factor) AS total_real_work
FROM tasks_todo
GROUP BY exe_id
UNION ALL
-- 协办任务
SELECT user_id AS exe_id, SUM(predict_work * (1 -evaluate_factor) / sub_executor_count) AS total_pre_work,
SUM(real_work * (1 - evaluate_factor) / sub_executor_count) AS total_real_work
FROM work_cal
GROUP BY exe_id
) AS init
GROUP BY exe_id
'''
cursor = connection.cursor()
cursor.execute(query1)
cursor.execute(query2)
cursor.execute(query3)
raw = cursor.fetchall()
return raw

16
apps/tasks/resources.py Normal file
View File

@ -0,0 +1,16 @@
from import_export import resources
from .models import Todo, Task
class TodoResources(resources.ModelResource):
class Meta:
model = Todo
fields = ('todo_topic', 'todo_note', 'deadline', 'duty_group__name', 'main_executor__real_name',
'sub_executor__real_name', 'sub_executor_count', 'related_task', 'predict_work', 'evaluate_factor',
'maturity', 'real_work', 'complete_note', 'quality_mark')
class TaskResources(resources.ModelResource):
class Meta:
model = Task

View File

@ -7,25 +7,35 @@ register = template.Library()
@register.filter(name='quarter_cate') @register.filter(name='quarter_cate')
def quarter_cate(value, quarter): def quarter_cate(value, year_quarter):
year_now = datetime.now().strftime('%Y')
month = value.deadline.strftime('%m') month = value.deadline.strftime('%m')
year = value.deadline.strftime('%Y')
month = int(month) month = int(month)
quarter = int(quarter) # year = int(year)
# year_now = int(year) 不知道为什么如果转整数会把2021和2022认为相同
# print(quarter)
req_year = str(year_quarter[1])
quarter = int(year_quarter[0])
# 可能造成性能损失,每次数据库会调出符合“当年”的任务或工作包的全部任务下工作包,并逐个判断
if year == req_year:
if quarter == 1 and 1 <= month <= 3: if quarter == 1 and 1 <= month <= 3:
return value return str(value) + '&#13;&#10;'
elif quarter == 2 and 4 <= month <= 6: elif quarter == 2 and 4 <= month <= 6:
return value return str(value) + '&#13;&#10;'
elif quarter == 3 and 7 <= month <= 9: elif quarter == 3 and 7 <= month <= 9:
return value return str(value) + '&#13;&#10;'
elif quarter == 4 and 10 <= month <= 12: elif quarter == 4 and 10 <= month <= 12:
return value return str(value) + '&#13;&#10;'
else: else:
return '' return ''
else:
return ''
@register.filter(name='last_month') @register.filter(name='last_month')
def last_month(value): def last_month(value):
@ -34,6 +44,7 @@ def last_month(value):
last_month = last_date.strftime('%Y/%m') last_month = last_date.strftime('%Y/%m')
return last_month return last_month
@register.filter(name='next_month') @register.filter(name='next_month')
def next_month(value): def next_month(value):
curent_date = datetime.strptime(value, '%Y年%m月') curent_date = datetime.strptime(value, '%Y年%m月')
@ -41,7 +52,28 @@ def next_month(value):
next_month = next_date.strftime('%Y/%m') next_month = next_date.strftime('%Y/%m')
return next_month return next_month
@register.filter(name='this_month') @register.filter(name='this_month')
def this_month(value): def this_month(value):
curent_date = datetime.strptime(value, '%Y年%m月') curent_date = datetime.strptime(value, '%Y年%m月')
return curent_date.strftime('%m') return curent_date.strftime('%m')
@register.filter(name='last_year')
def last_year(value):
curent_year = value[1]
last_year = curent_year - 1
return last_year
@register.filter(name='next_year')
def next_year(value):
curent_year = value[1]
next_year = curent_year + 1
return next_year
@register.filter(name='this_year')
def this_year(value):
curent_year = value[1]
return curent_year

View File

@ -1,3 +1,212 @@
import decimal
from django.core import serializers
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.functions import TruncMonth
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.
from django.db.models import Sum, F, FloatField, Count, Q
from django.utils import timezone
from django.views import View
from apps.users.models import User
from apps.tasks.models import Todo
import pandas as pd
from collections import defaultdict, Counter, ChainMap
from copy import deepcopy
# 用于按用户名合并任务会对指定字段进行累加类似GROUP BY(SUM),但会丢失无需计算的部分,因此之前需要单独构建姓名字典
def solve(dataset, group_by_key, sum_value_keys):
dic = defaultdict(Counter)
for item in dataset:
key = item[group_by_key]
vals = {k: item[k] for k in sum_value_keys}
dic[key].update(vals)
return dic
def cal_method(main_list, sub_list, user_name):
# print(main_list, sub_list, user_name)
# sub_credit = sub_list
# main_credit = main_list
total_data = []
season = 1
# 分别计算每个季度的工作量、评价
for main_credit, sub_credit in zip(main_list, sub_list):
# TODO 对于完成质量的核算,先对评价求和,再除以已评价的承办任务数
# for i in main_credit:
# print(i['main_executor__quality_mark__mark_value__mark_value'])
# print(main_credit)
quality_dict = {}
for i in main_credit:
quality_dict[i['username']] = []
if i['main_executor__quality_mark__mark_value__mark_value'] != None:
quality_dict[i['username']].append(i['main_executor__quality_mark__mark_value__mark_value'])
for key, value in quality_dict.items():
if value:
quality_dict[key] = sum(value) / len(value)
# print(quality_dict)
main_credit = solve(main_credit, 'username', ['main_pre_cal', 'main_real_cal', 'main_count', 'main_executor__evaluate_factor'])
main_credit = dict(main_credit)
sub_credit = solve(sub_credit, 'username', ['sub_pre_cal', 'sub_real_cal', 'sub_count'])
sub_credit = dict(sub_credit)
# 按用户名合并承办与协办任务字典
total_credit = deepcopy(main_credit)
for key in sub_credit.keys():
if key in total_credit:
total_credit[key].update(sub_credit[key])
else:
total_credit[key] = sub_credit[key]
# print(total_credit['admin']['sub_pre_cal'])
# 根据字典内容,计算总工作量
for key, value in total_credit.items():
# print(value)
value['pre_cal'] = value['sub_pre_cal'] + value['main_pre_cal']
value['real_cal'] = value['sub_real_cal'] + value['main_real_cal']
value['real_name'] = user_name[key]
value['season'] = season
# 由于不计算协办任务quality_dict会有空值情况
try:
value['quality'] = quality_dict[key]
except:
value['quality'] = 0
for value in total_credit.values():
# print(value)
real_cal_season = "real_cal_" + str(season)
value[real_cal_season] = value.pop("real_cal")
quality_season = "quality_" + str(season)
value[quality_season] = value.pop("quality")
# print(total_credit)
total_data.append(total_credit)
# print(season)
season += 1
# new_credit = []
# for item in total_credit:
# for key, value in item.items():
# print(total_data, season)
dd = defaultdict(list)
for d in total_data: # you can list as many input dicts as you want here
for key, value in d.items():
dd[key].append(value)
# print(dd)
return dd
class TestView(View):
def get(self, request, year=2021, month=7):
user_info = User.objects.filter(department=request.user.department) \
.values_list('username', 'real_name')
user_name = {}
for user in user_info:
user_name[user[0]] = user[1]
work_cal = User.objects.filter(department=request.user.department, main_executor__deadline__year=2021) \
.order_by('main_executor__deadline').values('main_executor__todo_topic', 'main_executor__deadline')
# 以用户表查询,按用户列出所参与承办、协办任务,并在之后按用户名分组合并。
main_credit = User.objects.filter(department=request.user.department,
main_executor__deadline__year=year) \
.annotate(main_count=Count('main_executor')) \
.order_by('username') \
.values('username', 'main_executor', 'main_executor__predict_work', 'main_executor__real_work',
'main_executor__evaluate_factor', 'main_executor__maturity', 'main_executor__quality_mark__mark_value__mark_value', 'main_executor__deadline', 'main_count') # 这里的annotate不知会不会有问题
sub_count = Todo.objects.filter(sub_executor__department=request.user.department,
deadline__year=year) \
.annotate(sub_count=Count('sub_executor')).values('id', 'sub_count')
sub_credit = User.objects.filter(department=request.user.department,
sub_executor__deadline__year=year) \
.order_by('username') \
.values('username', 'real_name', 'sub_executor', 'sub_executor__predict_work', 'sub_executor__real_work',
'sub_executor__evaluate_factor', 'sub_executor__maturity', 'sub_executor__quality_mark__mark_value__mark_value', 'sub_executor__deadline')
# 构建工作包id对应协办人人数的字典
sub_exe_count = {}
for i in sub_count:
key = i['id']
value = i['sub_count']
sub_exe_count[key] = value
# print(sub_exe_count)
# 计算每个承办任务的预计、实际工作量,并插入字典
for i in main_credit:
# 将成熟度由百分数转小数,以便其后与其他变量计算 eg. 50% -> 0.5
i['main_executor__maturity'] = decimal.Decimal(float(i['main_executor__maturity'].strip('%')) / 100)
# print(i['main_executor__maturity'])
i['main_pre_cal'] = i['main_executor__predict_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
i['main_real_cal'] = i['main_executor__real_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
# print(i)
# print(str(i['sub_executor']))
# 将协办任务对应的人数插入,计算每个协办任务的预计、实际工作量,并插入字典
for i in sub_credit:
sub_todo_id = i['sub_executor']
i['sub_exe_count'] = sub_exe_count[sub_todo_id]
i['sub_executor__maturity'] = decimal.Decimal(float(i['sub_executor__maturity'].strip('%')) / 100)
i['sub_pre_cal'] = i['sub_executor__predict_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
i['sub_real_cal'] = i['sub_executor__real_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
i['sub_count'] = 1 # 用于帮助累加程序计算每个用户的协办任务数量, sub_exe_count会返回此协办任务的协办人数在累加时导致计算错误
# print(i)
# print(str(i['sub_executor']))
main_Q1st, main_Q2nd, main_Q3th, main_Q4th = [], [], [], []
for i in main_credit:
# print(i['main_executor__deadline'].month)
deadline_month = i['main_executor__deadline'].month
if 1 <= deadline_month <= 3:
main_Q1st.append(i)
elif 4 <= deadline_month <= 6:
main_Q2nd.append(i)
elif 7 <= deadline_month <= 9:
main_Q3th.append(i)
elif 10 <= deadline_month <= 12:
main_Q4th.append(i)
# print(Q1st, Q2nd, Q3th, Q4th)
sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th = [], [], [], []
for i in sub_credit:
# print(i['main_executor__deadline'].month)
deadline_month = i['sub_executor__deadline'].month
if 1 <= deadline_month <= 3:
sub_Q1st.append(i)
elif 4 <= deadline_month <= 6:
sub_Q2nd.append(i)
elif 7 <= deadline_month <= 9:
sub_Q3th.append(i)
elif 10 <= deadline_month <= 12:
sub_Q4th.append(i)
main_list = [main_Q1st, main_Q2nd, main_Q3th, main_Q4th]
sub_list = [sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th]
result = cal_method(main_list, sub_list, user_name)
print(result)
stat = {}
for key, value in result.items():
stat[key] = {}
for j in value:
stat[key]['real_name'] = j['real_name']
season = str(j['season'])
real_cal_season = 'real_cal_' + season
quality_season = 'quality_' + season
stat[key][real_cal_season] = j[real_cal_season]
stat[key][quality_season] = j[quality_season]
print(j)
print(stat)
return HttpResponse(stat)
# return render(request, 'tasks/index.html', context)

View File

@ -1,9 +1,13 @@
from django.urls import path from django.urls import path, include
from apps.tasks import views # import debug_toolbar
from apps.tasks import views, tests
from TasksManager import settings
app_name = 'tasks' app_name = 'tasks'
urlpatterns = [ urlpatterns = [
path('', views.IndexView.as_view(), name='index'), path('', views.IndexView.as_view(), name='index'),
path('<int:year>/<int:month>/', views.IndexView.as_view(), name='index_month'),
path('test/', tests.TestView.as_view(), name='test'),
path('login/', views.UserLoginView.as_view(), name='login'), path('login/', views.UserLoginView.as_view(), name='login'),
path('logout/', views.UserLogoutView.as_view(), name='logout'), path('logout/', views.UserLogoutView.as_view(), name='logout'),
path('todolist/', views.TodoListView.as_view(), name='todolist'), path('todolist/', views.TodoListView.as_view(), name='todolist'),
@ -12,4 +16,13 @@ urlpatterns = [
path('group_todolist/<int:year>/<int:month>/', views.GroupTodoList.as_view(), name='group_todolist_month'), path('group_todolist/<int:year>/<int:month>/', views.GroupTodoList.as_view(), name='group_todolist_month'),
path('todo/<int:pk>/', views.TodoEntryView.as_view(), name='todo_detail'), path('todo/<int:pk>/', views.TodoEntryView.as_view(), name='todo_detail'),
path('tasklist/', views.TaskListView.as_view(), name='tasklist'), path('tasklist/', views.TaskListView.as_view(), name='tasklist'),
path('tasklist/<int:year>/', views.TaskListView.as_view(), name='tasklist_year'),
path('about/', views.AboutView.as_view(), name='about'),
] ]
#
# if settings.DEBUG:
# import debug_toolbar
# urlpatterns = [
# path('__debug__/', include(debug_toolbar.urls)),
# ] + urlpatterns

View File

@ -1,20 +1,363 @@
import re
from collections import defaultdict, Counter
from copy import deepcopy
from django.contrib import auth, messages from django.contrib import auth, messages
from django.contrib.auth import logout from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Sum, F, FloatField, Count, Q
from django.db.models.functions import Coalesce
from django.http import HttpResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
import django.utils.timezone as timezone import django.utils.timezone as timezone
from django.utils.http import url_has_allowed_host_and_scheme
from django.views import View from django.views import View
from django.db import connection
# Create your views here. # Create your views here.
from apps.tasks.models import Todo, Task from apps.tasks.models import Todo, Task
from apps.tasks.forms import TodoForm from apps.tasks.forms import TodoForm
from apps.users.models import User
from . import my_query
from functools import reduce
import pandas as pd
import decimal
class IndexView(View): class IndexView(View):
def get(self, request): @method_decorator(login_required)
tasks = Task.objects.filter().order_by('task_id') def get(self, request, year=None, month=None):
context = {'tasks': tasks} # 搞成了!!! 当月份更改时自动变更首页显示的月份! 其实就是用的上面那个链接的方法,之前不知道为啥没去用
return render(request, 'tasks/tasklist.html', context) # https://stackoverflow.com/questions/63072235/django-localdate-doesnt-return-correct-date
# https://stackoverflow.com/questions/13225890/django-default-timezone-now-saves-records-using-old-time
# https://stackoverflow.com/questions/38237777/django-timezone-now-vs-timezone-now
if year is None:
year = timezone.now().year
if month is None:
month = timezone.now().month
#TODO 部门不用部门下用户累加
# 建立username和真实姓名的对应字典并在工作量计算完成后插入结果集
user_info = User.objects.filter(department=request.user.department)\
.values_list('username', 'real_name')
user_name = {}
for user in user_info:
user_name[user[0]] = user[1]
# print(user_name)
# 以用户表查询,按用户列出所参与承办、协办任务,并在之后按用户名分组合并。
main_credit = User.objects.filter(department=request.user.department,
main_executor__deadline__year=year, main_executor__deadline__month=month)\
.annotate(main_count=Count('main_executor'))\
.order_by('username')\
.values('username', 'main_executor', 'main_executor__predict_work', 'main_executor__real_work',
'main_executor__evaluate_factor', 'main_count') # 这里的annotate不知会不会有问题
sub_count = Todo.objects.filter(sub_executor__department=request.user.department,
deadline__year=year, deadline__month=month) \
.annotate(sub_count=Count('sub_executor')).values('id', 'sub_count')
sub_credit = User.objects.filter(department=request.user.department,
sub_executor__deadline__year=year, sub_executor__deadline__month=month) \
.order_by('username') \
.values('username', 'real_name', 'sub_executor', 'sub_executor__predict_work', 'sub_executor__real_work',
'sub_executor__evaluate_factor')
# 构建工作包id对应协办人人数的字典
sub_exe_count = {}
for i in sub_count:
key = i['id']
value = i['sub_count']
sub_exe_count[key] = value
# print(sub_exe_count)
# 计算每个承办任务的预计、实际工作量,并插入字典
for i in main_credit:
i['main_pre_cal'] = i['main_executor__predict_work'] * i['main_executor__evaluate_factor']
i['main_real_cal'] = i['main_executor__real_work'] * i['main_executor__evaluate_factor']
# print(i)
# print(str(i['sub_executor']))
# 将协办任务对应的人数插入,计算每个协办任务的预计、实际工作量,并插入字典
for i in sub_credit:
sub_todo_id = i['sub_executor']
i['sub_exe_count'] = sub_exe_count[sub_todo_id]
i['sub_pre_cal'] = i['sub_executor__predict_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count']
i['sub_real_cal'] = i['sub_executor__real_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count']
i['sub_count'] = 1 # 用于帮助累加程序计算每个用户的协办任务数量, sub_exe_count会返回此协办任务的协办人数在累加时导致计算错误
# print(i)
# print(str(i['sub_executor']))
# 用于按用户名合并任务会对指定字段进行累加类似GROUP BY(SUM),但会丢失无需计算的部分,因此之前需要单独构建姓名字典
def solve(dataset, group_by_key, sum_value_keys):
dic = defaultdict(Counter)
for item in dataset:
key = item[group_by_key]
vals = {k: item[k] for k in sum_value_keys}
dic[key].update(vals)
return dic
main_credit = solve(main_credit, 'username', ['main_pre_cal', 'main_real_cal', 'main_count'])
main_credit = dict(main_credit)
sub_credit = solve(sub_credit, 'username', ['sub_pre_cal', 'sub_real_cal', 'sub_count'])
sub_credit = dict(sub_credit)
# 按用户名合并承办与协办任务字典
total_credit = deepcopy(main_credit)
for key in sub_credit.keys():
if key in total_credit:
total_credit[key].update(sub_credit[key])
else:
total_credit[key] = sub_credit[key]
# print(total_credit['admin']['sub_pre_cal'])
# 根据字典内容,计算总工作量
for key, value in total_credit.items():
# print(value)
value['pre_cal'] = value['sub_pre_cal'] + value['main_pre_cal']
value['real_cal'] = value['sub_real_cal'] + value['main_real_cal']
value['real_name'] = user_name[key]
# total_credit = dict(main_credit.items() + sub_credit.items())
# for value in sub_credit.values():
# dict(value)
# print(sub_credit)
#
# new_pair = {}
# for doc, tab in sub_credit.items():
# new_pair[doc] = {}
# for word, freq in tab.items():
# new_pair[doc][word] = freq
# print(new_pair)
# return HttpResponse(str(main_credit)+str(sub_credit))
# 若total_credit为空不进行以下操作避免err
if total_credit:
# 暂时解决当该用户当月无任务时total_credit中不包含该用户的用户名导致Key Error
try:
current_user = total_credit[request.user.username]
except:
current_user = {}
# 累加该部门各个用户的工作量,计算部门工作量
department_cal = {}
temp_pre = []
depart_pre, depart_real, depart_count = 0, 0, 0
for username, value in total_credit.items():
print(username)
print(value['pre_cal'])
depart_pre = depart_pre + value['pre_cal']
depart_real = depart_real + value['real_cal']
depart_count = depart_count + value['main_count']
temp_pre.append(value['pre_cal'])
department_cal['pre_cal'] = depart_pre
department_cal['real_cal'] = depart_real
department_cal['depart_count'] = depart_count
department_cal['pre_avg'] = department_cal['pre_cal'] / len(total_credit)
department_cal['real_avg'] = department_cal['real_cal'] / len(total_credit)
else:
department_cal, current_user = {}, {}
# 计算实际工作量、完成质量
def cal_method(main_list, sub_list, user_name):
# print(main_list, sub_list, user_name)
# sub_credit = sub_list
# main_credit = main_list
total_data = []
season = 1
# 分别计算每个季度的工作量、评价
for main_credit, sub_credit in zip(main_list, sub_list):
# TODO 对于完成质量的核算,先对评价求和,再除以已评价的承办任务数
# for i in main_credit:
# print(i['main_executor__quality_mark__mark_value__mark_value'])
# print(main_credit)
quality_dict = {}
for i in main_credit:
# 先赋值0避免前端显示个[]
quality_dict[i['username']] = []
if i['main_executor__quality_mark__mark_value__mark_value'] != None:
quality_dict[i['username']].append(i['main_executor__quality_mark__mark_value__mark_value'])
# TEST
for i in sub_credit:
# 先赋值0避免前端显示个[]
quality_dict[i['username']] = []
if i['sub_executor__quality_mark__mark_value__mark_value'] != None:
quality_dict[i['username']].append(i['sub_executor__quality_mark__mark_value__mark_value'])
for key, value in quality_dict.items():
if value:
quality_dict[key] = sum(value) / len(value)
# print(quality_dict)
main_credit = solve(main_credit, 'username',
['main_pre_cal', 'main_real_cal', 'main_count', 'main_executor__evaluate_factor'])
main_credit = dict(main_credit)
sub_credit = solve(sub_credit, 'username', ['sub_pre_cal', 'sub_real_cal', 'sub_count'])
sub_credit = dict(sub_credit)
# 按用户名合并承办与协办任务字典
total_credit = deepcopy(main_credit)
for key in sub_credit.keys():
if key in total_credit:
total_credit[key].update(sub_credit[key])
else:
total_credit[key] = sub_credit[key]
# print(total_credit['admin']['sub_pre_cal'])
# 根据字典内容,计算总工作量
for key, value in total_credit.items():
# print(value)
value['pre_cal'] = value['sub_pre_cal'] + value['main_pre_cal']
value['real_cal'] = value['sub_real_cal'] + value['main_real_cal']
value['real_name'] = user_name[key]
value['season'] = season
# 由于不计算协办任务quality_dict会有空值情况
try:
value['quality'] = quality_dict[key]
except:
value['quality'] = 0
for value in total_credit.values():
# print(value)
real_cal_season = "real_cal_" + str(season)
value[real_cal_season] = value.pop("real_cal")
quality_season = "quality_" + str(season)
value[quality_season] = value.pop("quality")
# print(total_credit)
total_data.append(total_credit)
# print(season)
season += 1
# new_credit = []
# for item in total_credit:
# for key, value in item.items():
# print(total_data, season)
dd = defaultdict(list)
for d in total_data: # you can list as many input dicts as you want here
for key, value in d.items():
dd[key].append(value)
# print(dd)
return dd
user_info = User.objects.filter(department=request.user.department) \
.values_list('username', 'real_name')
user_name = {}
for user in user_info:
user_name[user[0]] = user[1]
work_cal = User.objects.filter(department=request.user.department, main_executor__deadline__year=2021) \
.order_by('main_executor__deadline').values('main_executor__todo_topic', 'main_executor__deadline')
# 以用户表查询,按用户列出所参与承办、协办任务,并在之后按用户名分组合并。
main_credit = User.objects.filter(department=request.user.department,
main_executor__deadline__year=year) \
.annotate(main_count=Count('main_executor')) \
.order_by('username') \
.values('username', 'main_executor', 'main_executor__predict_work', 'main_executor__real_work',
'main_executor__evaluate_factor', 'main_executor__maturity', 'main_executor__quality_mark__mark_value__mark_value', 'main_executor__deadline', 'main_count') # 这里的annotate不知会不会有问题
sub_count = Todo.objects.filter(sub_executor__department=request.user.department,
deadline__year=year) \
.annotate(sub_count=Count('sub_executor')).values('id', 'sub_count')
sub_credit = User.objects.filter(department=request.user.department,
sub_executor__deadline__year=year) \
.order_by('username') \
.values('username', 'real_name', 'sub_executor', 'sub_executor__predict_work', 'sub_executor__real_work',
'sub_executor__evaluate_factor', 'sub_executor__maturity', 'sub_executor__quality_mark__mark_value__mark_value', 'sub_executor__deadline')
# 构建工作包id对应协办人人数的字典
sub_exe_count = {}
for i in sub_count:
key = i['id']
value = i['sub_count']
sub_exe_count[key] = value
# print(sub_exe_count)
# 计算每个承办任务的预计、实际工作量、成熟度,并插入字典
for i in main_credit:
# 将成熟度由百分数转小数,以便其后与其他变量计算 eg. 50% -> 0.5
print('ad', i['main_executor__maturity'])
# 临时补丁,解决用户将成熟度设置为空的问题,后面的协办任务也改了,记得改回去
# TODO 数据库中设置成熟度为非空
try:
i['main_executor__maturity'] = decimal.Decimal(float(i['main_executor__maturity'].strip('%')) / 100)
except:
i['main_executor__maturity'] = decimal.Decimal(float(0) / 100)
# print(i['main_executor__maturity'])
i['main_pre_cal'] = i['main_executor__predict_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
i['main_real_cal'] = i['main_executor__real_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
# print(i)
# print(str(i['sub_executor']))
# 将协办任务对应的人数插入,计算每个协办任务的预计、实际工作量,并插入字典
for i in sub_credit:
sub_todo_id = i['sub_executor']
i['sub_exe_count'] = sub_exe_count[sub_todo_id]
try:
i['sub_executor__maturity'] = decimal.Decimal(float(i['sub_executor__maturity'].strip('%')) / 100)
except:
i['sub_executor__maturity'] = decimal.Decimal(float(0) / 100)
i['sub_pre_cal'] = i['sub_executor__predict_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
i['sub_real_cal'] = i['sub_executor__real_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
i['sub_count'] = 1 # 用于帮助累加程序计算每个用户的协办任务数量, sub_exe_count会返回此协办任务的协办人数在累加时导致计算错误
# print(i)
# print(str(i['sub_executor']))
main_Q1st, main_Q2nd, main_Q3th, main_Q4th = [], [], [], []
for i in main_credit:
# print(i['main_executor__deadline'].month)
deadline_month = i['main_executor__deadline'].month
if 1 <= deadline_month <= 3:
main_Q1st.append(i)
elif 4 <= deadline_month <= 6:
main_Q2nd.append(i)
elif 7 <= deadline_month <= 9:
main_Q3th.append(i)
elif 10 <= deadline_month <= 12:
main_Q4th.append(i)
# print(Q1st, Q2nd, Q3th, Q4th)
sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th = [], [], [], []
for i in sub_credit:
# print(i['main_executor__deadline'].month)
deadline_month = i['sub_executor__deadline'].month
if 1 <= deadline_month <= 3:
sub_Q1st.append(i)
elif 4 <= deadline_month <= 6:
sub_Q2nd.append(i)
elif 7 <= deadline_month <= 9:
sub_Q3th.append(i)
elif 10 <= deadline_month <= 12:
sub_Q4th.append(i)
main_list = [main_Q1st, main_Q2nd, main_Q3th, main_Q4th]
sub_list = [sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th]
stat_result = cal_method(main_list, sub_list, user_name)
stat = {}
for key, value in stat_result.items():
stat[key] = {}
for j in value:
stat[key]['real_name'] = j['real_name']
season = str(j['season'])
real_cal_season = 'real_cal_' + season
quality_season = 'quality_' + season
stat[key][real_cal_season] = j[real_cal_season]
stat[key][quality_season] = j[quality_season]
print(stat_result)
# return HttpResponse(result.items())
# 为页面提供日期信息
date = str(year) + '' + str(month) + ''
# return HttpResponse(str(total_credit) + '\n' + str(department_cal))
context = {'date': date, 'users_data': total_credit, 'department_cal': department_cal,
'current_user': current_user, 'stat': stat}
return render(request, 'tasks/index.html', context)
class TodoListView(View): class TodoListView(View):
@ -23,26 +366,36 @@ class TodoListView(View):
my_todo = Todo.objects.filter(main_executor=request.user, deadline__year=year, deadline__month=month) my_todo = Todo.objects.filter(main_executor=request.user, deadline__year=year, deadline__month=month)
my_sub_todo = Todo.objects.filter(sub_executor=request.user, deadline__year=year, deadline__month=month) my_sub_todo = Todo.objects.filter(sub_executor=request.user, deadline__year=year, deadline__month=month)
date = str(year) + '' + str(month) + '' date = str(year) + '' + str(month) + ''
context = {'my_todo': my_todo, 'my_sub_todo': my_sub_todo, 'date': date} current_path = request.get_full_path()
context = {'my_todo': my_todo, 'my_sub_todo': my_sub_todo, 'date': date, 'current_path': current_path}
return render(request, 'tasks/todolist.html', context) return render(request, 'tasks/todolist.html', context)
class GroupTodoList(View): class GroupTodoList(View):
@method_decorator(login_required) @method_decorator(login_required)
def get(self, request, year=timezone.now().year, month=timezone.now().month): def get(self, request, year=timezone.now().year, month=timezone.now().month):
group_todo = Todo.objects.filter(main_executor__department=request.user.department, deadline__year=year, deadline__month=month) group_todo = Todo.objects.filter(main_executor__department=request.user.department, deadline__year=year,
deadline__month=month).order_by('related_task_id', 'deadline')
date = str(year) + '' + str(month) + '' date = str(year) + '' + str(month) + ''
context = {'group_todo': group_todo, 'date': date} current_path = request.get_full_path()
context = {'group_todo': group_todo, 'date': date, 'current_path': current_path}
return render(request, 'tasks/group_todolist.html', context) return render(request, 'tasks/group_todolist.html', context)
class TaskListView(View): class TaskListView(View):
@method_decorator(login_required) @method_decorator(login_required)
def get(self, request): def get(self, request, year=timezone.now().year): # TODO 把timezone.now().year写在后面要用year替换的地方是否可以解决
tasks = Task.objects.filter().order_by('task_id') tasks = Task.objects.filter(department=request.user.department, deadline__year=year).order_by('task_id')\
context = {'tasks': tasks} | Task.objects.filter(department=request.user.department, related_task__deadline__year=year).order_by('task_id')
tasks = tasks.distinct()
# tasks = Task.objects.filter(Q(department=request.user.department), Q(deadline__year=year) | Q(related_task__deadline__year=year)).order_by('task_id')
# 使用‘或’,找出工作包/年度任务的截止日期在今年的年度任务。后面还要做一个筛选,以达到只显示本年度的工作包
year_quarter = {'1': [1, year], '2': [2, year], '3': [3, year], '4': [4, year]}
context = {'tasks': tasks, 'year_quarter': year_quarter}
return render(request, 'tasks/tasklist.html', context) return render(request, 'tasks/tasklist.html', context)
class UserLoginView(View): class UserLoginView(View):
def get(self, request): def get(self, request):
return render(request, 'tasks/login.html') return render(request, 'tasks/login.html')
@ -58,6 +411,7 @@ class UserLoginView(View):
else: else:
return redirect('tasks:index') return redirect('tasks:index')
class UserLogoutView(View): class UserLogoutView(View):
def get(self, request): def get(self, request):
logout(request) logout(request)
@ -74,7 +428,16 @@ class TodoEntryView(View):
def post(self, request, pk): def post(self, request, pk):
todo_detail = Todo.objects.get(id=pk) todo_detail = Todo.objects.get(id=pk)
form = TodoForm(instance=todo_detail, data=request.POST) form = TodoForm(instance=todo_detail, data=request.POST)
redirect_to = request.GET.get('next')
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect('tasks:index') if url_has_allowed_host_and_scheme(redirect_to, None):
return redirect(redirect_to)
else:
return redirect('tasks:todolist')
# return redirect('tasks:todo_detail', pk=pk) # return redirect('tasks:todo_detail', pk=pk)
class AboutView(View):
def get(self, request):
return render(request, 'tasks/about.html')

View File

@ -23,23 +23,54 @@ class MyUserAdmin(UserAdmin):
class MyGroupAdmin(GroupAdmin): class MyGroupAdmin(GroupAdmin):
pass pass
class DepartmentAdmin(admin.ModelAdmin): class DepartmentAdmin(admin.ModelAdmin):
pass pass
class TaskPropertyAdmin(admin.ModelAdmin): class TaskPropertyAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'task_property', 'own_department' 'task_property', 'own_department'
) )
def get_queryset(self, request):
qs = super(TaskPropertyAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(own_department=request.user.department)
class QualityMarkAdmin(admin.ModelAdmin): class QualityMarkAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'mark_name', 'mark_name',
'mark_value' 'mark_value'
) )
def mark_value(self):
return self.mark_value
mark_value.short_description = 'ss'
# # 仅显示当前部门的任务属性,除非为超管
# def get_queryset(self, request):
# qs = super(QualityMarkAdmin, self).get_queryset(request)
# if request.user.is_superuser:
# return qs
# else:
# return qs.filter(department=request.user.department)
class MarkValueAdmin(admin.ModelAdmin):
# def get_queryset(self, request):
# qs = super(MarkValueAdmin, self).get_queryset(request)
# if request.user.is_superuser:
# return qs
# else:
# return qs.filter(department=request.user.department)
pass
admin.site.register(models.User, MyUserAdmin) admin.site.register(models.User, MyUserAdmin)
admin.site.register(models.MyGroup, MyGroupAdmin) admin.site.register(models.MyGroup, MyGroupAdmin)
admin.site.register(models.MarkValue) admin.site.register(models.MarkValue, MarkValueAdmin)
admin.site.register(models.Department) admin.site.register(models.Department)
admin.site.register(models.QualityMark, QualityMarkAdmin) admin.site.register(models.QualityMark, QualityMarkAdmin)
admin.site.register(models.TaskProperty, TaskPropertyAdmin) admin.site.register(models.TaskProperty, TaskPropertyAdmin)

View File

@ -1,97 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 13:31
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='Department',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='部门名称')),
],
options={
'verbose_name': '部门',
},
),
migrations.CreateModel(
name='MarkValue',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mark_value', models.DecimalField(decimal_places=2, max_digits=3)),
],
),
migrations.CreateModel(
name='MyGroup',
fields=[
('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.group')),
],
options={
'verbose_name': '权限组',
'verbose_name_plural': '权限组',
},
bases=('auth.group',),
managers=[
('objects', django.contrib.auth.models.GroupManager()),
],
),
migrations.CreateModel(
name='TaskProperty',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_property', models.CharField(max_length=50, verbose_name='任务属性')),
],
options={
'verbose_name': '任务属性',
'verbose_name_plural': '任务属性',
},
),
migrations.CreateModel(
name='QualityMark',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mark_name', models.CharField(max_length=10, verbose_name='评分')),
('mark_value', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.markvalue')),
],
),
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('real_name', models.CharField(max_length=150, verbose_name='姓名')),
('staff_id', models.CharField(max_length=150, verbose_name='工号')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.department')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': '用户',
'verbose_name_plural': '用户',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 13:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='user',
name='department',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.department'),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 14:17
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20210129_1332'),
]
operations = [
migrations.AlterModelOptions(
name='department',
options={'verbose_name': '部门', 'verbose_name_plural': '部门'},
),
migrations.AddField(
model_name='taskproperty',
name='own_department',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='users.department'),
preserve_default=False,
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 14:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0003_auto_20210129_1417'),
]
operations = [
migrations.AlterField(
model_name='taskproperty',
name='own_department',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.department', verbose_name='所属部门'),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-30 20:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0004_auto_20210129_1434'),
]
operations = [
migrations.AlterField(
model_name='taskproperty',
name='own_department',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.department', verbose_name='所属部门'),
),
migrations.AlterField(
model_name='user',
name='department',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.department'),
),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-01 19:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0005_auto_20210130_2021'),
]
operations = [
migrations.AlterModelOptions(
name='markvalue',
options={'verbose_name': '评分值', 'verbose_name_plural': '评分值'},
),
migrations.AlterModelOptions(
name='qualitymark',
options={'verbose_name': '评分名称', 'verbose_name_plural': '评分名称'},
),
migrations.AlterField(
model_name='markvalue',
name='mark_value',
field=models.DecimalField(decimal_places=2, max_digits=3, verbose_name='评分值'),
),
migrations.AlterField(
model_name='qualitymark',
name='mark_name',
field=models.CharField(max_length=10, verbose_name='评分名称'),
),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-01 22:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0006_auto_20210201_1900'),
]
operations = [
migrations.AlterModelOptions(
name='markvalue',
options={'verbose_name': '评价等级考核系数', 'verbose_name_plural': '评价等级考核系数'},
),
migrations.AlterModelOptions(
name='qualitymark',
options={'verbose_name': '评价等级定义', 'verbose_name_plural': '评价等级定义'},
),
migrations.AlterField(
model_name='markvalue',
name='mark_value',
field=models.DecimalField(decimal_places=2, max_digits=3, verbose_name='评价等级考核系数'),
),
migrations.AlterField(
model_name='qualitymark',
name='mark_name',
field=models.CharField(max_length=10, verbose_name='评价等级定义'),
),
]

View File

@ -3,10 +3,13 @@ from django.contrib.auth.models import AbstractUser, Group
# Create your models here. # Create your models here.
from django.db.models import Avg, Sum, F, Value
class User(AbstractUser): class User(AbstractUser):
real_name = models.CharField(max_length=150, verbose_name='姓名') real_name = models.CharField(max_length=150, verbose_name='姓名')
staff_id = models.CharField(max_length=150, verbose_name='工号') staff_id = models.CharField(max_length=150, verbose_name='工号')
department = models.ForeignKey('Department', on_delete=models.SET_NULL, null=True, blank=True) department = models.ForeignKey('Department', related_name='member', on_delete=models.SET_NULL, null=True, blank=True)
class Meta: class Meta:
verbose_name = '用户' verbose_name = '用户'
@ -15,6 +18,16 @@ class User(AbstractUser):
def __str__(self): def __str__(self):
return self.real_name return self.real_name
@classmethod
def get_total_point(cls):
return cls.objects.annotate(total=Sum('main_executor__predict_work'))
# def get_total_point(cls):
# return cls.objects.aggregate(total=Sum(F('main_executor__predict_work') * F('main_executor__evaluate_factor') + F('sub_executor__predict_work') * F('sub_executor__evaluate_factor')))['total']
@classmethod
def get_predict_work_count(cls):
return cls.objects.aggregate(total_predict_work=Sum('main_executor__predict_work'))
class MyGroup(Group): class MyGroup(Group):
class Meta: class Meta:
@ -32,6 +45,10 @@ class Department(models.Model):
verbose_name = '部门' verbose_name = '部门'
verbose_name_plural = '部门' verbose_name_plural = '部门'
@property
def get_user_number(self):
return self.member.count()
class MarkValue(models.Model): class MarkValue(models.Model):
mark_value = models.DecimalField('评价等级考核系数', max_digits=3, decimal_places=2) mark_value = models.DecimalField('评价等级考核系数', max_digits=3, decimal_places=2)

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
version: "3"
services:
app:
restart: always
build: . # '点'代表当前目录
command: "python3 manage.py runserver 0.0.0.0:8000"
volumes:
- .:/code
ports:
- "8000:8000"

7
docs/.vuepress/client.js Normal file
View File

@ -0,0 +1,7 @@
import { defineClientConfig } from '@vuepress/client'
export default defineClientConfig({
enhance({ app, router, siteData }) {},
setup() {},
rootComponents: [],
})

37
docs/.vuepress/config.js Normal file
View File

@ -0,0 +1,37 @@
// module.exports = {
// lang: 'zh-CN',
// title: 'TasksManager',
// description: '超好用的任务分配管理系统',
// }
//
import {defaultTheme, defineUserConfig} from 'vuepress'
export default defineUserConfig({
lang: 'zh-CN',
title: 'TasksManager',
description: '超好用的任务分配管理系统',
theme: defaultTheme({
navbar: [
{
text: '指南',
link: '/guide/',
},
{
text: '部署',
link: '/install/',
}
],
sidebar: "auto",
repo: 'https://github.com/raiots/TasksManager',
repoLabel: '✨Github',
docsDir: 'docs',
docsBranch: 'master',
lastUpdatedText: "📑 最后更新",
contributorsText: "💕 参与贡献",
editLinkText: "🖊️ 编辑本文",
notFound: ["👻 页面不存在"],
}),
})

20
docs/README.md Normal file
View File

@ -0,0 +1,20 @@
---
home: true
heroText: TasksManager
tagline: 超好用的任务分配管理工具❤️
actions:
- text: 项目简介
link: /guide/
type: primary
- text: 快速部署
link: /install/
type: secondary
features:
- title: 任务分发
details: TasksManager 管理员能够以年度任务为主线,向用户发送待办任务
- title: 任务提交
details: 用户可以查看自己的待办任务、并提交完成度与完成情况说明,帮助管理员掌握部门工作情况
- title: 评价考核
details: TasksManager 会对用户完成任务数量与质量进行综合评价,并直观显示于首页
---

6
docs/guide/README.md Normal file
View File

@ -0,0 +1,6 @@
---
sidebar: auto
---
TasksManager 是一个由Django开发的任务分发系统

81
docs/install/README.md Normal file
View File

@ -0,0 +1,81 @@
---
sidebar: auto
navbar: True
---
# 部署
你可以使用两种方式部署 TasksManager这里推荐使用 Docker compose 的方式进行部署。
## Docker Compose 部署
> Docker Compose 是一个用于在使用Compose 文件格式定义的 Docker 上运行多容器应用程序的工具。
### 安装
拉取 TaskManager 程序源码
```shell
git clone https://github.com/raiots/TasksManager.git
```
### 启动
使用 cd 命令进入程序文件夹后启动程序:
```shell
docker-compose up -d
```
TasksManager 将会运行在服务器的 8000 端口,在浏览器中打开 http://ip地址:8000 即可访问
## 使用源码手动部署
> 程序使用 Python3.8 开发,请提前配置 Python 环境
### 安装
使用 git 下载 TasksManager 源码:
```shell
git clone https://github.com/raiots/TasksManager.git
cd TasksManager
```
创建并激活 Python 虚拟环境
```shell
python -m venv venv
.\venv\Scripts\activate.sh
```
安装 TasksManager 依赖
```shell
pip install -r requirements.txt
```
### 启动
```shell
python3 manage.py runserver 0.0.0.0:8000
```
TasksManager 将会运行在服务器的 8000 端口,在浏览器中打开 http://ip地址:8000 即可访问
## 使用 Nginx 配置反向代理
```
server
{
listen 443 ssl http2;
server_name your.domain.com ;
location / {
proxy_pass http://127.0.0.1:8000; # 转发规则
proxy_set_header Host $proxy_host; # 修改转发请求头让8000端口的应用可以受到真实的请求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"devDependencies": {
"vuepress": "^2.0.0-beta.60"
},
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
}

View File

@ -1,7 +1,21 @@
asgiref==3.3.1 asgiref==3.3.1
Django==3.1.5 defusedxml==0.7.1
django-simpleui==2021.1.1 diff-match-patch==20200713
Django==3.1.12
django-import-export==2.5.0
django-simpleui==2021.8.1
et-xmlfile==1.0.1
MarkupPy==1.14
numpy==1.20.2
odfpy==1.4.1
openpyxl==3.0.7
pandas==1.2.4
python-dateutil==2.8.1 python-dateutil==2.8.1
pytz==2020.5 pytz==2020.5
PyYAML==5.4.1
six==1.15.0 six==1.15.0
sqlparse==0.4.1 sqlparse==0.4.1
tablib==3.0.0
xlrd==2.0.1
xlwt==1.3.0
django-debug-toolbar~=3.2.2

40
static/tasks/about_font.css vendored Normal file
View File

@ -0,0 +1,40 @@
/* cyrillic-ext */
@font-face {
font-family: 'Press Start 2P';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/pressstart2p/v9/e3t4euO8T-267oIAQAu6jDQyK3nYivN04w.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Press Start 2P';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/pressstart2p/v9/e3t4euO8T-267oIAQAu6jDQyK3nRivN04w.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'Press Start 2P';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/pressstart2p/v9/e3t4euO8T-267oIAQAu6jDQyK3nWivN04w.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* latin-ext */
@font-face {
font-family: 'Press Start 2P';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/pressstart2p/v9/e3t4euO8T-267oIAQAu6jDQyK3nbivN04w.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Press Start 2P';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/pressstart2p/v9/e3t4euO8T-267oIAQAu6jDQyK3nVivM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

11
static/tasks/nes.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
static/tasks/pixel.ttf vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
BORDER-TOP: 0px solid; BORDER-TOP: 0px solid;
/*width:100%;*/ /*width:100%;*/
overflow:hidden; overflow:hidden;
width:200px; width:160px;
font-size: 0.5rem; font-size: 0.5rem;
} }
@ -13,3 +13,8 @@
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
.col-xs-1-5 {
width: 20%;
float: left;
}

234
templates/tasks/about.html Normal file
View File

@ -0,0 +1,234 @@
{% load static %}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ABOUT - Raiot</title>
<link rel="stylesheet" href="{% static 'tasks/nes.min.css' %}">
<style>
@charset "utf-8";
@import url({% static 'tasks/about_font.css' %});
@font-face {
font-family: 'pixel';
src: url('{% static 'tasks/pixel.ttf' %}');
}
body {
padding: 0 2rem;
margin: 0 2rem;
}
#nescss>.container {
max-width: 980px;
margin: 0 auto;
margin-top: 150px;
}
main p {
font-size: 1.2rem;
}
h2 {
font-size: 1.6rem;
}
/* Header */
header {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 9;
border-bottom: 4px solid #D3D3D3;
background-color: white;
}
header>.container {
display: flex;
align-items: baseline;
max-width: 980px;
margin: 0 auto;
padding-top: 1rem;
transition: all 0.2s ease;
}
header>.container>.nav-brand {
margin-right: auto;
}
.nav-brand .brand-logo {
margin-right: 1rem;
}
.nav-brand>a {
color: #212529;
text-decoration: none;
}
/* Main */
.main-content {
margin-bottom: 4rem;
}
/* Footer */
footer {
text-align: center;
margin-bottom: 2rem;
}
footer a {
color: #333;
text-decoration: none;
}
h2>a {
margin-right: 1rem;
}
.topic {
margin-bottom: 3rem;
font-family: 'pixel';
}
/* Containers */
.item.containers>.nes-container {
display: inline-block;
max-width: 400px;
}
/* Topic */
h3.topic-title {
display: flex;
align-items: center;
margin-bottom: 1rem;
}
h3.topic-title>i {
margin-right: 1.5rem;
}
@media screen and (max-width: calc(980px - 4rem)) {
header>.container {
margin: 0 4rem;
}
}
@media screen and (max-width: 768px) {
body {
margin: 2rem 0.5rem;
padding: 0;
}
header>.container {
margin: 0 0.5rem;
}
.github-link {
display: none;
}
.message-list>.message>.nes-balloon {
max-width: 60%;
}
}
</style>
</head>
<body cz-shotcut-listen="true">
<div id="nescss">
<header class="">
<div class="container">
<div class="nav-brand">
<a href="#">
<h1>STAFF</h1>
</a>
<p>©&nbsp;2021 RAIOT</p>
</div>
</div>
</header>
<div class="container">
<main class="main-content">
<section class="topic">
<h2>
<a href="#">#</a>
MIT许可证
</h2>
<p>
特此免费授予任何获得副本的人这个软件和相关的文档文件(“软件”)来处理在软件中没有限制,包括但不限于权利使用,复制,
修改,合并,发布,分发,再许可和/或销售该软件的副本,并允许软件所属的人员提供这样做,但须符合以下条件:
<br>
上述版权声明和本许可声明必须包含在内本软件的副本或实质性部分。
<br>
本软件按“原样”提供,不附有任何形式的明示或暗示保证默示的,包括但不限于对适销性的保证,适用于特定目的和不侵权。在任何
情况下,作者或版权持有者对任何索赔,损坏或其他责任均不负任何责任责任,无论是在合同,民事侵权行为或其他方面,与本软件
或本软件的使用或其他交易有关或与之有关软件。
</p>
</section>
<section class="topic">
<h2>
<a href="#">#</a>
关于我
</h2>
<p>
Django开发的练习之作请不要在意混乱的代码
</p>
</section>
<section class="topic">
<h2>
<a href="#">#</a>
免责声明
</h2>
<p>您当前所访问的站点使用的是下面所提到的开源程序。您能看到这个页面仅代表这个站点使用了本程序,其使用过程中发生的一切问题需要其自行解决,本程序不承担任何责任。</p>
<div class="nes-table-responsive">
<table class="nes-table is-bordered">
<tbody>
<tr>
<td>作者</td>
<td><a href="#">Raiot.Me</a></td>
</tr>
<tr>
<td>维护</td>
<td>
<a href="#">Raiot.Me</a>
</td>
</tr>
<tr>
<td>鸣谢</td>
<td>所有被引用过代码的同学以及所有提交过PR的同学这份About界面框架的作者。当然还有在使用这份程序的你我Ta。</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="topic">
<h2>
<a href="#">#</a>
Credits
</h2>
<div class="lists">
<ul class="nes-list is-circle">
<li>
<a href="#" target="_blank" rel="noopener">
NES.css
</a>
&nbsp;-&nbsp;NES-style CSS Framework
</li>
<li>
<a href="#" target="_blank" rel="noopener">Simple-UI</a>
&nbsp;-&nbsp;Django后台模板
</li>
<li>
<a href="#" target="_blank" rel="noopener">AdminLTE</a>
&nbsp;-&nbsp;Free Bootstrap Admin Template
</li>
</ul>
</div>
</section>
</main>
</div>
</div>
</body>
</html>

View File

@ -9,7 +9,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>任务进度管理系统</title> <title>任务管理工具</title>
<!-- Google Font: Source Sans Pro --> <!-- Google Font: Source Sans Pro -->
<link rel="stylesheet" href="{% static 'tasks/dist/css/googlefont.css' %}"> <link rel="stylesheet" href="{% static 'tasks/dist/css/googlefont.css' %}">
@ -32,7 +32,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
<div class="container-xl"> <div class="container-xl">
<a href="{% url 'tasks:index' %}" class="navbar-brand"> <a href="{% url 'tasks:index' %}" class="navbar-brand">
<img src="{% static 'tasks/dist/img/AdminLTELogo.png' %}" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8"> <img src="{% static 'tasks/dist/img/AdminLTELogo.png' %}" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
<span class="brand-text font-weight-light">任务管理系统</span> <span class="brand-text font-weight-light">任务管理工具</span>
</a> </a>
<button class="navbar-toggler order-1" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler order-1" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
@ -53,10 +53,18 @@ scratch. This page gets rid of all links and provides the needed markup only.
<a href="{% url 'tasks:tasklist' %}" class="nav-link">年度任务</a> <a href="{% url 'tasks:tasklist' %}" class="nav-link">年度任务</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="/admin" class="nav-link">编辑任务</a> <a href="/admin/#tasks/task" class="nav-link">编辑任务</a>
</li>
<li class="nav-item dropdown">
<a id="dropdownSubMenu1" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle">管理域风险分析</a>
<ul aria-labelledby="dropdownSubMenu1" class="dropdown-menu border-0 shadow">
<li><a href="{% url 'tasks:todolist' %}" class="dropdown-item" style="font-size: 0.9rem"> 风险事件采集 </a></li>
<li><a href="{% url 'tasks:group_todolist' %}" class="dropdown-item" style="font-size: 0.9rem">风险事件批量采集</a></li>
<li><a href="{% url 'tasks:group_todolist' %}" class="dropdown-item" style="font-size: 0.9rem">可视化分析</a></li>
</ul>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="/admin" class="nav-link">系统配置</a> <a href="/admin/#users/user" class="nav-link">系统配置</a>
</li> </li>
{# <li class="nav-item dropdown">#} {# <li class="nav-item dropdown">#}
{# <a id="dropdownSubMenu1" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle">统计与分析</a>#} {# <a id="dropdownSubMenu1" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle">统计与分析</a>#}
@ -189,7 +197,9 @@ scratch. This page gets rid of all links and provides the needed markup only.
{# </li>#} {# </li>#}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="nav-tabs"> <li class="nav-tabs">
<a>欢迎,{{ user.real_name }}</a> <a>欢迎,</a>
<b style="color: cadetblue">{{ user.real_name }}</b>
<a>。 本软件为非涉密应用工具,禁止处理秘密级及以上信息!!!</a>
</li> </li>
<li class="navbar-tabs"> <li class="navbar-tabs">
<a href="{% url 'tasks:logout' %}">&nbsp;登出</a> <a href="{% url 'tasks:logout' %}">&nbsp;登出</a>
@ -302,10 +312,10 @@ scratch. This page gets rid of all links and provides the needed markup only.
<footer class="main-footer"> <footer class="main-footer">
<!-- To the right --> <!-- To the right -->
<div class="float-right d-none d-sm-inline"> <div class="float-right d-none d-sm-inline">
CHRDI Task Manager <a href="#">ver 0.5 - beta</a> CHRDI Task Manager <a href="{% url 'tasks:about' %}">ver 0.6 - beta</a>
</div> </div>
<!-- Default to the left --> <!-- Default to the left -->
<strong>Made with ❤ by <a href="https://raiot.me">Raintony</a>.</strong> <strong>Made with ❤ by <a href="{% url 'tasks:about' %}">Raiot</a>.</strong>
</footer> </footer>
</div> </div>
<!-- ./wrapper --> <!-- ./wrapper -->
@ -319,5 +329,8 @@ scratch. This page gets rid of all links and provides the needed markup only.
<script src="{% static 'tasks/plugins/bootstrap/js/bootstrap.bundle.min.js' %}"></script> <script src="{% static 'tasks/plugins/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
<!-- AdminLTE App --> <!-- AdminLTE App -->
<script src="{% static 'tasks/dist/js/adminlte.min.js' %}"></script> <script src="{% static 'tasks/dist/js/adminlte.min.js' %}"></script>
{% block script %}
{% endblock %}
</body> </body>
</html> </html>

View File

@ -58,18 +58,20 @@
<table class="table" style="word-break: break-all"> <table class="table" style="word-break: break-all">
<thead> <thead>
<tr> <tr>
<th style="width: 10px; text-align:center; vertical-align: middle;">#</th> <th style="width: 16px; text-align:center; vertical-align: middle;">序号</th>
<th style="width: 280px; text-align:center; vertical-align: middle;">工作事项</th> <th style="width: 280px; text-align:center; vertical-align: middle;">工作事项</th>
<th style="text-align:center; vertical-align: middle;">完成时间</th> <th style="width: 100px; text-align:center; vertical-align: middle;">完成时间</th>
<th style="text-align:center; vertical-align: middle;">工作要求及交付物</th> <th style="width: 160px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
<th style="text-align:center; vertical-align: middle; width: 120px">承办单位</th> <th style="width: 60px; text-align:center; vertical-align: middle;">任务编号</th>
<th style="width: 120px; text-align:center; vertical-align: middle;">任务来源</th>
<th style="width: 120px; text-align:center; vertical-align: middle;">承办单位</th>
<th style="width: 120px;text-align:center; vertical-align: middle;">承/督办人</th> <th style="width: 120px;text-align:center; vertical-align: middle;">承/督办人</th>
<th style="width: 200px;text-align:center; vertical-align: middle;">协办人</th> <th style="width: 200px;text-align:center; vertical-align: middle;">协办人</th>
<th style="width: 80px; text-align:center; vertical-align: middle;">工作量(pre)</th> <th style="width: 80px; text-align:center; vertical-align: middle;">工作量(pre)</th>
<th style="width: 60px; text-align:center; vertical-align: middle;">折算系数</th> <th style="width: 60px; text-align:center; vertical-align: middle;">折算系数</th>
<th style="width: 80px; text-align:center; vertical-align: middle;">工作量</th> <th style="width: 80px; text-align:center; vertical-align: middle;">工作量</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">成熟度</th> <th style="width: 100px; text-align:center; vertical-align: middle;">成熟度</th>
<th style="text-align:center; vertical-align: middle;">完成情况</th> <th style="width: 220px; text-align:center; vertical-align: middle;">完成情况</th>
<th style="width: 120px; text-align:center; vertical-align: middle;">完成质量</th> <th style="width: 120px; text-align:center; vertical-align: middle;">完成质量</th>
{# <th style="width: 40px">Label</th>#} {# <th style="width: 40px">Label</th>#}
</tr> </tr>
@ -77,12 +79,14 @@
<tbody> <tbody>
{% for todo in group_todo %} {% for todo in group_todo %}
<tr> <tr>
<td>{{ todo.id }}</td> <td>{{ forloop.counter }}</td>
<td>{{ todo.todo_topic }}</td> <td>{{ todo.todo_topic }}</td>
<td style="text-align: center">{{ todo.deadline | date:"m月d日" }}</td> <td style="text-align: center">{{ todo.deadline | date:"m月d日" }}</td>
<td style="width: 160px"><textarea style="width:100%;overflow:hidden">{{ todo.todo_note }}</textarea></td> <td style="width: 160px"><textarea style="width:100%;overflow:hidden">{{ todo.todo_note }}</textarea></td>
<td style="text-align: center">{{ todo.task_id }}</td>
<td style="text-align: center">{{ todo.task_origin }}</td>
<td style="text-align: center">{{ todo.duty_group }}</td> <td style="text-align: center">{{ todo.duty_group }}</td>
<td style="text-align: center">{{ todo.main_executor.all|join:", " }}</td> <td style="text-align: center">{{ todo.main_executor}}</td>
<td style="text-align: center">{{ todo.sub_executor.all|join:", " }}</td> <td style="text-align: center">{{ todo.sub_executor.all|join:", " }}</td>
<td style="text-align:center;">{{ todo.predict_work }}</td> <td style="text-align:center;">{{ todo.predict_work }}</td>
<td style="text-align:center;">{{ todo.evaluate_factor }}</td> <td style="text-align:center;">{{ todo.evaluate_factor }}</td>
@ -93,7 +97,7 @@
</div> </div>
<span class="badge bg-gradient-blue">{{ todo.maturity }}</span> <span class="badge bg-gradient-blue">{{ todo.maturity }}</span>
</td> </td>
<td style="text-align: center"><textarea style="overflow:hidden; width: 300px">{{ todo.complete_note }}</textarea></td> <td style="text-align: center"><textarea style="overflow:hidden;width:100%;">{{ todo.complete_note }}</textarea></td>
<td style="text-align: center">{{ todo.quality_mark|default:'' }}</td> <td style="text-align: center">{{ todo.quality_mark|default:'' }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -1 +1,455 @@
{% extends 'tasks/base.html' %} {% extends 'tasks/base.html' %}
{% load static %}
{% block main_content %}
<div class="content-wrapper" style="min-height: 1200.88px;">
<!-- Content Header (Page header) -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>总览 ({{ date }}) </h1>
{# <h1><font color="#FF0000">测试版,请勿用于生产环境!!!</font></h1>#}
</div>
<div class="col-sm-6">
{# <ol class="breadcrumb float-sm-right">#}
{# <li class="breadcrumb-item active"><a href="#">主页</a></li>#}
{# </ol>#}
<br>
{% load taskfilter %}
<ul class="pagination float-md-right">
<li class="page-item"><a class="page-link" href="/{{ date|last_month }}">«</a></li>
<li class="page-item"><a class="page-link"> {{ date|this_month }}月 </a></li>
<li class="page-item"><a class="page-link" href="/{{ date|next_month }}">»</a></li>
</ul>
</div>
</div>
</div><!-- /.container-fluid -->
</section>
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col">
<div class="info-box">
<span class="info-box-icon bg-danger elevation-1"><i class="fas fa-tasks"></i></span>
<div class="info-box-content">
<span class="info-box-text">部门总任务数</span>
<span class="info-box-number">
{{ department_cal.depart_count|floatformat:'0'|default_if_none:'0' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
<div class="info-box-content">
<span class="info-box-text">部门预计总工作量</span>
<span class="info-box-number">
{{ department_cal.pre_cal|default_if_none:'0'|floatformat:'1' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
<div class="info-box-content">
<span class="info-box-text">部门实际总工作量</span>
<span class="info-box-number">
{{ department_cal.real_cal|default_if_none:'0'|floatformat:'1' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
<div class="info-box-content">
<span class="info-box-text">部门预计平均工作量</span>
<span class="info-box-number">
{{ department_cal.pre_avg|default_if_none:'0'|floatformat:'1' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
<div class="info-box-content">
<span class="info-box-text">部门实际平均工作量</span>
<span class="info-box-number">
{{ department_cal.real_avg|default_if_none:'0'|floatformat:'1' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<!-- fix for small devices only -->
<div class="clearfix hidden-md-up"></div>
{# <div class="col-12 col-sm-6 col-md-3">#}
{# <div class="info-box mb-3">#}
{# <span class="info-box-icon bg-success elevation-1"><i class="fas fa-shopping-cart"></i></span>#}
{##}
{# <div class="info-box-content">#}
{# <span class="info-box-text">Sales</span>#}
{# <span class="info-box-number">760</span>#}
{# </div>#}
{# <!-- /.info-box-content -->#}
{# </div>#}
{# <!-- /.info-box -->#}
{# </div>#}
{# <!-- /.col -->#}
{# <div class="col-12 col-sm-6 col-md-3">#}
{# <div class="info-box mb-3">#}
{# <span class="info-box-icon bg-warning elevation-1"><i class="fas fa-users"></i></span>#}
{##}
{# <div class="info-box-content">#}
{# <span class="info-box-text">New Members</span>#}
{# <span class="info-box-number">2,000</span>#}
{# </div>#}
{# <!-- /.info-box-content -->#}
{# </div>#}
{# <!-- /.info-box -->#}
{# </div>#}
<!-- /.col -->
</div>
<div class="row">
<div class="col">
<div class="info-box">
<span class="info-box-icon bg-info elevation-1"><i class="fas fa-tasks"></i></span>
<div class="info-box-content">
<span class="info-box-text"><b>个人任务数据统计:</b></span>
{# <span class="info-box-number">#}
{# {% for user in current_user %}{{ user.main_executor_count }}{% endfor %}#}
{# <small></small>#}
{# </span>#}
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<div class="col">
<div class="info-box">
{# <span class="info-box-icon bg-info elevation-1"><i class="fas fa-tasks"></i></span>#}
<div class="info-box-content">
<span class="info-box-text">承办任务数</span>
<span class="info-box-number">
{{ current_user.main_count }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
{# <span class="info-box-icon bg-danger elevation-1"><i class="fas fa-tasks"></i></span>#}
<div class="info-box-content">
<span class="info-box-text">协办任务数</span>
<span class="info-box-number">
{{ current_user.sub_count }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
<div class="info-box-content">
<span class="info-box-text">预计工作量</span>
<span class="info-box-number">
{{ current_user.pre_cal|floatformat:'1'|default_if_none:'0' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<div class="col">
<div class="info-box mb-3">
<div class="info-box-content">
<span class="info-box-text">实际工作量</span>
<span class="info-box-number">
{{ current_user.real_cal|floatformat:'1'|default_if_none:'0' }}
<small></small>
</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
<!-- fix for small devices only -->
<div class="clearfix hidden-md-up"></div>
{# <div class="col-12 col-sm-6 col-md-3">#}
{# <div class="info-box mb-3">#}
{# <span class="info-box-icon bg-success elevation-1"><i class="fas fa-shopping-cart"></i></span>#}
{##}
{# <div class="info-box-content">#}
{# <span class="info-box-text">Sales</span>#}
{# <span class="info-box-number">760</span>#}
{# </div>#}
{# <!-- /.info-box-content -->#}
{# </div>#}
{# <!-- /.info-box -->#}
{# </div>#}
{# <!-- /.col -->#}
{# <div class="col-12 col-sm-6 col-md-3">#}
{# <div class="info-box mb-3">#}
{# <span class="info-box-icon bg-warning elevation-1"><i class="fas fa-users"></i></span>#}
{##}
{# <div class="info-box-content">#}
{# <span class="info-box-text">New Members</span>#}
{# <span class="info-box-number">2,000</span>#}
{# </div>#}
{# <!-- /.info-box-content -->#}
{# </div>#}
{# <!-- /.info-box -->#}
{# </div>#}
<!-- /.col -->
</div>
<div class="row">
<div class="col-md-12">
<!-- BAR CHART -->
<div class="card card-gray">
<div class="card-header">
<h3 class="card-title">本部门工作统计表</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse"><i
class="fas fa-minus"></i>
</button>
<button type="button" class="btn btn-tool" data-card-widget="remove"><i
class="fas fa-times"></i></button>
</div>
</div>
<!-- 工作统计表格 -->
<div class="card-body">
<div class="chart">
<div class="chartjs-size-monitor">
<div class="chartjs-size-monitor-expand">
<div class=""></div>
</div>
<div class="chartjs-size-monitor-shrink">
<div class=""></div>
</div>
</div>
<canvas id="barChart"
style="height: 230px; min-height: 230px; display: block; width: 764px;"
width="764" height="230" class="chartjs-render-monitor"></canvas>
</div>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
<!-- /.col (MAIN) -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">部门工作统计表</h3>
</div>
{# TODO 更换为绩效考核表 分季度按人显示工作量及完成质量#}
<!-- /.card-header -->
<div class="card-body">
<div id="example2_wrapper" class="dataTables_wrapper dt-bootstrap4"><div class="row"><div class="col-sm-12 col-md-6"></div><div class="col-sm-12 col-md-6"></div></div><div class="row"><div class="col-sm-12"><table id="example2" class="table table-bordered table-hover dataTable" role="grid" aria-describedby="example2_info">
<thead>
<tr role="row">
<th class="sorting_asc" tabindex="0" aria-controls="example2" rowspan="2" colspan="1" aria-sort="ascending" aria-label="Rendering engine: activate to sort column descending">姓名</th>
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">一季度</th>
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">二季度</th>
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">三季度</th>
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">四季度</th>
</tr>
<tr role="row">
<th>实际工作量</th>
<th>完成质量</th>
<th>实际工作量</th>
<th>完成质量</th>
<th>实际工作量</th>
<th>完成质量</th>
<th>实际工作量</th>
<th>完成质量</th>
</tr>
</thead>
<tbody>
{% for user in stat.values %}
<tr role="row" class="odd">
<td class="sorting_1">{{ user.real_name }}</td>
<td>{{ user.real_cal_1|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.quality_1|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.real_cal_2|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.quality_2|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.real_cal_3|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.quality_3|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.real_cal_4|default_if_none:'0'|floatformat:'1' }}</td>
<td>{{ user.quality_4|default_if_none:'0'|floatformat:'1' }}</td>
</tr>
{% endfor %}
</tbody>
{# <tfoot>#}
{# <tr><th rowspan="1" colspan="1">Rendering engine</th><th rowspan="1" colspan="1">Browser</th><th rowspan="1" colspan="1">Platform(s)</th><th rowspan="1" colspan="1">Engine version</th><th rowspan="1" colspan="1">CSS grade</th></tr>#}
{# </tfoot>#}
</table></div></div>
</div>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
<!-- /.col -->
</div>
</div><!-- /.container-fluid -->
</section>
<!-- /.content -->
</div>
{# <div class="content-wrapper">#}
{# {{ main_points }}#}
{# <br>#}
{# {{ sub_points }}#}
{# <br>#}
{# {{ points }}#}
{# {{ points.3.credit }}#}
{# {% for point in points.values %}'{{ point }}', {% endfor %}#}
{# {% for point in points %}#}
{# {{ point }}#}
{# {% endfor %}#}
{##}
{# {% for user in users %}#}
{# {{ user }}#}
{# {{ user.get_predict_work_count.total_predict_work }}#}
{# {{ user.get_total_point }}#}
{# {{ user.main_executor.count }}#}
{# {% for todo in user.main_executor.all %}#}
{# {{ todo }}#}
{# {{ todo.points }}#}
{# {% endfor %}#}
{# </br>#}
{# {% endfor %}#}
{##}
{# </div>#}
{% endblock %}
{% block script %}
<!-- ChartJS -->
<script src="{% static 'tasks/plugins/chart.js/Chart.min.js' %}"></script>
<script>
$(function () {
/* ChartJS
* -------
* Here we will create a few charts using ChartJS
*/
var areaChartData = {
labels : [{% for user in users_data.values %}'{{ user.real_name }}', {% endfor %}],
datasets: [
{
label : '协办任务',
backgroundColor : 'rgba(114,140,212,1)',
borderColor : 'rgba(114,140,212,1)',
pointRadius : false,
pointColor : 'rgba(114,140,212,1)',
pointStrokeColor : '#c1c7d1',
pointHighlightFill : '#fff',
pointHighlightStroke: 'rgba(220,220,220,1)',
data : [{% for user in users_data.values %}'{{ user.sub_count }}', {% endfor %}]
},
{
label : '承办任务',
backgroundColor : 'rgba(23,162,184,0.9)',
borderColor : 'rgba(100,98,204,0.8)',
pointRadius : false,
pointColor : '#3b8bba',
pointStrokeColor : 'rgba(60,141,188,1)',
pointHighlightFill : '#fff',
pointHighlightStroke: 'rgba(60,141,188,1)',
data : [{% for user in users_data.values %}'{{ user.main_count }}', {% endfor %}]
},
{
label : '预计工作量',
backgroundColor : 'rgba(210, 214, 222,1)',
borderColor : 'rgba(210, 214, 222,1)',
pointRadius : false,
pointColor : '#3b8bba',
pointStrokeColor : 'rgba(60,141,188,1)',
pointHighlightFill : '#fff',
pointHighlightStroke: 'rgba(60,141,188,1)',
data : [{% for user in users_data.values %}'{{ user.pre_cal|floatformat:'1' }}', {% endfor %}]
},
{
label : '工作量',
backgroundColor : 'rgba(253,199,101,0.9)',
borderColor : 'rgba(253,199,101,0.8)',
pointRadius : false,
pointColor : '#3b8bba',
pointStrokeColor : 'rgba(60,141,188,1)',
pointHighlightFill : '#fff',
pointHighlightStroke: 'rgba(60,141,188,1)',
data : [{% for user in users_data.values %}'{{ user.real_cal|floatformat:'1' }}', {% endfor %}]
},
]
}
//-------------
//- BAR CHART -
//-------------
var barChartCanvas = $('#barChart').get(0).getContext('2d')
var barChartData = jQuery.extend(true, {}, areaChartData)
var temp0 = areaChartData.datasets[0]
var temp1 = areaChartData.datasets[1]
barChartData.datasets[0] = temp1
barChartData.datasets[1] = temp0
var barChartOptions = {
responsive : true,
maintainAspectRatio : false,
datasetFill : false
}
var barChart = new Chart(barChartCanvas, {
type: 'bar',
data: barChartData,
options: barChartOptions
})
})
</script>
{% endblock %}

View File

@ -5,7 +5,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>AdminLTE 3 | Log in (v2)</title> <title>任务管理工具</title>
<!-- Google Font: Source Sans Pro --> <!-- Google Font: Source Sans Pro -->
<link rel="stylesheet" href="{% static 'tasks/dist/css/googlefont.css' %}"> <link rel="stylesheet" href="{% static 'tasks/dist/css/googlefont.css' %}">
@ -16,12 +16,12 @@
<!-- Theme style --> <!-- Theme style -->
<link rel="stylesheet" href="{% static 'tasks/dist/css/adminlte.min.css' %}"> <link rel="stylesheet" href="{% static 'tasks/dist/css/adminlte.min.css' %}">
</head> </head>
<body class="hold-transition login-page"> <body class="hold-transition login-page" style="background-image: url({% static 'tasks/images/peak_background.svg' %})">
<div class="login-box"> <div class="login-box">
<!-- /.login-logo --> <!-- /.login-logo -->
<div class="card card-outline card-primary"> <div class="card card-outline card-primary">
<div class="card-header text-center"> <div class="card-header text-center">
<a href="../../index2.html" class="h1"><b>任务</b>管理系统</a> <a href="{% url 'tasks:index' %}" class="h1"><b>任务</b>管理系统</a>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="login-box-msg">登录以继续</p> <p class="login-box-msg">登录以继续</p>
@ -68,9 +68,9 @@
{% csrf_token %} {% csrf_token %}
</form> </form>
<p class="mb-1"> {# <p class="mb-1">#}
<a href="forgot-password.html">无法登录?</a> {# <a href="#">无法登录?</a>#}
</p> {# </p>#}
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
</div> </div>

View File

@ -33,13 +33,26 @@
<!-- /.card-header --> <!-- /.card-header -->
<div class="card-body p-0"> <div class="card-body p-0">
<table class="table table-striped" style="word-break: break-all; overflow: auto" cellspacing="0"> <table class="table table-striped" style="word-break: break-all; overflow: auto" cellspacing="0">
<div class="row">
<div class="col-12" style="background-color: #f2f2f2">
<button type="button" id="unfold" onclick="alert1()" class="btn btn-default float-left" style="display: block;"><i class="far fa-credit-card"></i> 展开</button>
<button type="button" id="fold" onclick="alert1()" class="btn btn-default float-left" style="display: none;"><i class="far fa-credit-card"></i> 收起</button>
<ul class="pagination float-md-right">
{% load taskfilter %}
<li class="page-item"><a class="page-link" href="/tasklist/{{ year_quarter.1|last_year }}">«</a></li>
<li class="page-item"><a class="page-link"> {{ year_quarter.1|this_year }}年 </a></li>
<li class="page-item"><a class="page-link" href="/tasklist/{{ year_quarter.1|next_year }}">»</a></li>
</ul>
</div>
</div>
<thead> <thead>
<tr style="background-color: #f2f2f2"> <tr style="background-color: #f2f2f2">
<th style="width: 100px; vertical-align: middle;">任务属性</th> <th style="width: 140px; text-align:center; vertical-align: middle;">任务属性</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">任务编号</th> <th style="width: 60px; text-align:center; vertical-align: middle;">任务编号</th>
<th style="width: 200px; text-align:center; vertical-align: middle;">任务名称</th> <th style="width: 220px; text-align:center; vertical-align: middle;">任务名称</th>
<th style="text-align:center; vertical-align: middle;">任务来源</th> <th style="width: 120px; text-align:center; vertical-align: middle;">任务来源</th>
<th style="width: 80px;text-align:center; vertical-align: middle;">目标值</th> <th style="width: 120px;text-align:center; vertical-align: middle;">目标值</th>
<th style="text-align:center; vertical-align: middle; padding: 0" colspan="4"> <th style="text-align:center; vertical-align: middle; padding: 0" colspan="4">
<table class="table" style="margin: 0;" cellpadding="0"> <table class="table" style="margin: 0;" cellpadding="0">
<tr style="background-color: #f2f2f2"><th colspan="4">任务显性目标及节点</th></tr> <tr style="background-color: #f2f2f2"><th colspan="4">任务显性目标及节点</th></tr>
@ -62,40 +75,40 @@
{# </thead>#} {# </thead>#}
</table> </table>
</th> </th>
<th style="width: 100px; text-align:center; vertical-align: middle;">完成日期</th> <th style="width: 80px; text-align:center; vertical-align: middle;">完成日期</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">责任单位</th> <th style="width: 100px; text-align:center; vertical-align: middle;">责任单位</th>
<th style="width: 80px; text-align:center; vertical-align: middle;">负责人</th> <th style="width: 100px; text-align:center; vertical-align: middle;">负责人</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">主管领导</th> <th style="width: 100px; text-align:center; vertical-align: middle;">主管领导</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">任务说明</th> <th style="width: 240px; text-align:center; vertical-align: middle;">任务说明</th>
{# <th style="width: 40px">Label</th>#} {# <th style="width: 40px">Label</th>#}
</tr> </tr>
</thead> </thead>
<tbody style="font-size: 0.9rem"> <tbody style="font-size: 0.9rem">
{% for task in tasks %} {% for task in tasks %}
<tr style="background-color: white"> <tr style="background-color: white">
<td>{{ task.task_property }}</td> <td style="text-align:center;">{{ task.task_property }}</td>
<td style="text-align:center;">{{ task.task_id }}</td> <td style="text-align:center;">{{ task.task_id }}</td>
<td>{{ task.task_topic }}</td> <td>{{ task.task_topic }}</td>
<td style="width: 160px">{{ task.task_origin }}</td> <td style="text-align:center;">{{ task.task_origin }}</td>
<td>{{ task.aim_value }}</td> <td>{{ task.aim_value }}</td>
{% load taskfilter %} {% load taskfilter %}
<td style="background-color: #BFE7A8; padding: 0"> <td style="background-color: #BFE7A8; padding: 0">
<textarea class="text-none-border" style="background-color: #BFE7A8;">{% for todo in task.related_task.all %}{{ todo|quarter_cate:'1' }}&#13;&#10;{% endfor %}</textarea> <textarea class="text-none-border" style="background-color: #BFE7A8;">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.1|safe }}{% endfor %}</textarea>
</td> </td>
<td style="background-color: #F5CCB0; padding: 0"> <td style="background-color: #F5CCB0; padding: 0">
<textarea class="text-none-border" style="background-color: #F5CCB0">{% for todo in task.related_task.all %}{{ todo|quarter_cate:'2' }}&#13;&#10;{% endfor %}</textarea> <textarea class="text-none-border" style="background-color: #F5CCB0">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.2|safe }}{% endfor %}</textarea>
</td> </td>
<td style="background-color: #F7E4A8; padding: 0"> <td style="background-color: #F7E4A8; padding: 0">
<textarea class="text-none-border" style="background-color: #F7E4A8">{% for todo in task.related_task.all %} {{ todo|quarter_cate:'3' }}&#13;&#10;{% endfor %}</textarea> <textarea class="text-none-border" style="background-color: #F7E4A8">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.3|safe }}{% endfor %}</textarea>
</td> </td>
<td style="background-color: #83E1E3; padding: 0"> <td style="background-color: #83E1E3; padding: 0">
<textarea class="text-none-border" style="background-color: #83E1E3">{% for todo in task.related_task.all %} {{ todo|quarter_cate:'4' }}&#13;&#10;{% endfor %}</textarea> <textarea class="text-none-border" style="background-color: #83E1E3">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.4|safe }}{% endfor %}</textarea>
</td> </td>
<td>{{ task.deadline | date:"Y年m月" }}</td> <td style="text-align:center;">{{ task.deadline | date:"Y.m" }}</td>
<td style="text-align:center;">{{ task.duty_group }}</td> <td style="text-align:center;">{{ task.duty_group }}</td>
<td style="text-align:center;">{{ task.principal }}</td> <td style="text-align:center;">{{ task.principal }}</td>
<td style="text-align:center;">{{ task.leader }}</td> <td style="text-align:center;">{{ task.leader|default_if_none:"" }}</td>
<td><textarea>{{ task.task_note }}</textarea></td> <td><textarea style="overflow:hidden;width:100%;">{{ task.task_note }}</textarea></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -111,4 +124,44 @@
<!-- /.content --> <!-- /.content -->
</div> </div>
<!-- /.content-wrapper --> <!-- /.content-wrapper -->
<script>
function setHeight(element) {
$(element).css({'height':'auto','overflow-y':'hidden'}).height(element.scrollHeight);
}
$('textarea').each(function () {
setHeight(this);
}).on('input', function () {
setHeight(this);
});
</script>
{% endblock %} {% endblock %}
{% block script %}
<script>
$('#unfold').click(function (element){
var unfold = document.getElementById("unfold");
var fold = document.getElementById("fold");
unfold.style.display = "none";
fold.style.display = "block";
$(element).css({'height':'auto','overflow-y':'hidden'}).height(element.scrollHeight);
$('textarea').each(function () {
setHeight(this);
}).on('input', function () {
setHeight(this);
});
})
$('#fold').click(function (element){
var unfold = document.getElementById("unfold");
var fold = document.getElementById("fold");
unfold.style.display = "block";
fold.style.display = "none";
location.reload();
// TODO 暂时无法解决让textarea恢复原本高度的问题使用刷新代替
})
</script>
{% endblock %}

View File

@ -9,7 +9,7 @@
<div class="container"> <div class="container">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1 class="m-0"> 我的任务 <small>Example 3.0</small></h1> <h1 class="m-0"> 我的任务 <small></small></h1>
</div><!-- /.col --> </div><!-- /.col -->
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
@ -26,26 +26,40 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header bg-gray">
<h3 class="card-title"> <h3 class="card-title">
<i class="fas fa-text-width"></i> <i class="fas fa-text-width"></i>
任务详情 任务详情
</h3> </h3>
</div> </div>
<!-- /.card-header --> <!-- /.card-header -->
<div class="card-body"> <div class="card-body bg-gray-light">
<dl class="row"> <dl class="row">
<dt class="col-sm-4">工作事项</dt> <dt class="col-sm-4">工作事项</dt>
<dd class="col-sm-8">{{ todo_detail.todo_topic }}</dd> <dd class="col-sm-8">{{ todo_detail.todo_topic }}</dd>
<dt class="col-sm-4">完成时间</dt>
<dd class="col-sm-8">{{ todo_detail.deadline }}</dd>
{# <dd class="col-sm-8 offset-sm-4">Donec id elit non mi porta gravida at eget metus.</dd>#}
<dt class="col-sm-4">交付物</dt> <dt class="col-sm-4">交付物</dt>
<dd class="col-sm-8">{{ todo_detail.todo_note }}</dd> <dd class="col-sm-8">{{ todo_detail.todo_note }}</dd>
{# <dd class="col-sm-8 offset-sm-4">Donec id elit non mi porta gravida at eget metus.</dd>#} <dt class="col-sm-4">任务编号</dt>
<dd class="col-sm-8">{{ todo_detail.task_id }}</dd>
<dt class="col-sm-4">任务来源</dt>
<dd class="col-sm-8">{{ todo_detail.task_origin }}</dd>
<dt class="col-sm-4">责任单位</dt> <dt class="col-sm-4">责任单位</dt>
<dd class="col-sm-8">{{ todo_detail.duty_group }}</dd> <dd class="col-sm-8">{{ todo_detail.duty_group }}</dd>
<dt class="col-sm-4">责任人</dt> <dt class="col-sm-4">承/督办人</dt>
<dd class="col-sm-8">Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo <dd class="col-sm-8">{{ todo_detail.main_executor }}</dd>
sit amet risus. <dt class="col-sm-4">协办人</dt>
</dd> <dd class="col-sm-8">{{ todo_detail.sub_executor.all|join:', ' }}</dd>
<dt class="col-sm-4">预计工作量</dt>
<dd class="col-sm-8">{{ todo_detail.predict_work }}</dd>
<dt class="col-sm-4">折算系数</dt>
<dd class="col-sm-8">{{ todo_detail.evaluate_factor }}</dd>
<dt class="col-sm-4">实际工作量</dt>
<dd class="col-sm-8">{{ todo_detail.real_work }}</dd>
<dt class="col-sm-4">成熟度</dt>
<dd class="col-sm-8">{{ todo_detail.maturity }}</dd>
</dl> </dl>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
@ -55,17 +69,41 @@
<div class="col-md-6"> <div class="col-md-6">
<!-- general form elements --> <!-- general form elements -->
<div class="card card-primary"> <div class="card card-primary">
<div class="card-header"> <div class="card-header bg-gray">
<h3 class="card-title">Quick Example</h3> <h3 class="card-title">更新工作包</h3>
</div> </div>
<!-- /.card-header --> <!-- /.card-header -->
<!-- form start --> <!-- form start -->
<form action="" method="post"> <form action="" method="post">
<div class="card-body"> <div class="card-body">
<div class="row">
<div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<label for="exampleInputEmail1">成熟度</label> <label for="exampleInputEmail1">成熟度</label>
{{ form.maturity }} {{ form.maturity }}
</div> </div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="exampleInputEmail1">实际工作量</label>
{{ form.real_work }}
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="exampleInputEmail">协办人</label>
{{ form.sub_executor }}
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="exampleInputEmail">折算系数</label>
{{ form.evaluate_factor }}
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="exampleInputPassword1">完成情况说明</label> <label for="exampleInputPassword1">完成情况说明</label>
{{ form.complete_note }} {{ form.complete_note }}

View File

@ -61,17 +61,17 @@
<h3 style="text-align: center; letter-spacing: 30px">我的承办</h3> <h3 style="text-align: center; letter-spacing: 30px">我的承办</h3>
<HR width="50%" color=#e2e4e6 SIZE=3> <HR width="50%" color=#e2e4e6 SIZE=3>
<tr> <tr>
<th style="width: 80px; text-align:center; vertical-align: middle;">序号</th> <th style="width: 100px; text-align:center; vertical-align: middle;">序号</th>
<th style="width: 280px; text-align:center; vertical-align: middle;">工作事项</th> <th style="width: 300px; text-align:center; vertical-align: middle;">工作事项</th>
<th style="text-align:center; vertical-align: middle;">完成时间</th> <th style="width: 100px; text-align:center; vertical-align: middle;">完成时间</th>
<th style="text-align:center; vertical-align: middle;">工作要求及交付物</th> <th style="width: 240px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
<th style="text-align:center; vertical-align: middle; width: 120px">承办单位</th> <th style="width: 140px; text-align:center; vertical-align: middle;">承办单位</th>
<th style="width: 200px; text-align:center; vertical-align: middle;">协办人</th> <th style="width: 200px; text-align:center; vertical-align: middle;">协办人</th>
<th style="width: 80px; text-align:center; vertical-align: middle;">工作量(pre)</th> <th style="width: 80px; text-align:center; vertical-align: middle;">工作量(pre)</th>
<th style="width: 60px; text-align:center; vertical-align: middle;">折算系数</th> <th style="width: 60px; text-align:center; vertical-align: middle;">折算系数</th>
<th style="width: 80px; text-align:center; vertical-align: middle;">工作量</th> <th style="width: 80px; text-align:center; vertical-align: middle;">工作量</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">成熟度</th> <th style="width: 100px; text-align:center; vertical-align: middle;">成熟度</th>
<th style="text-align:center; vertical-align: middle;">完成情况</th> <th style="width: 320px; text-align:center; vertical-align: middle;">完成情况</th>
<th style="width: 120px; text-align:center; vertical-align: middle;">完成质量</th> <th style="width: 120px; text-align:center; vertical-align: middle;">完成质量</th>
<th style="text-align:center; vertical-align: middle;"></th> <th style="text-align:center; vertical-align: middle;"></th>
{# <th style="width: 40px">Label</th>#} {# <th style="width: 40px">Label</th>#}
@ -80,10 +80,10 @@
<tbody> <tbody>
{% for todo in my_todo %} {% for todo in my_todo %}
<tr> <tr>
<td style="text-align: center">{{ todo.id }}</td> <td style="text-align: center">{{ forloop.counter }}</td>
<td>{{ todo.todo_topic }}</td> <td>{{ todo.todo_topic }}</td>
<td style="text-align: center">{{ todo.deadline | date:"m月d日" }}</td> <td style="text-align: center">{{ todo.deadline | date:"m月d日" }}</td>
<td style="width: 160px"><textarea style="width:100%;overflow:hidden">{{ todo.todo_note }}</textarea></td> <td><textarea style="width:100%;overflow:hidden">{{ todo.todo_note }}</textarea></td>
<td style="text-align: center">{{ todo.duty_group }}</td> <td style="text-align: center">{{ todo.duty_group }}</td>
<td style="text-align: center">{{ todo.sub_executor.all|join:", " }}</td> <td style="text-align: center">{{ todo.sub_executor.all|join:", " }}</td>
<td style="text-align:center;">{{ todo.predict_work }}</td> <td style="text-align:center;">{{ todo.predict_work }}</td>
@ -95,9 +95,9 @@
</div> </div>
<span class="badge bg-gradient-blue">{{ todo.maturity }}</span> <span class="badge bg-gradient-blue">{{ todo.maturity }}</span>
</td> </td>
<td style="text-align: center"><textarea style="overflow:hidden; width: 300px">{{ todo.complete_note }}</textarea></td> <td style="text-align: center"><textarea style="overflow:hidden; width: 100%" readonly="readonly">{{ todo.complete_note }}</textarea></td>
<td style="text-align: center">{{ todo.quality_mark|default:'' }}</td> <td style="text-align: center">{{ todo.quality_mark|default:'' }}</td>
<td><a href="{% url 'tasks:todo_detail' pk=todo.id %}">填报</a> </td> <td><a href="{% url 'tasks:todo_detail' pk=todo.id %}?next={{ current_path }}">填报</a> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -112,18 +112,18 @@
<h3 style="text-align: center; letter-spacing: 30px">我的协办</h3> <h3 style="text-align: center; letter-spacing: 30px">我的协办</h3>
<HR width="50%" color=#e2e4e6 SIZE=3> <HR width="50%" color=#e2e4e6 SIZE=3>
<tr> <tr>
<th style="width: 80px; text-align:center; vertical-align: middle;">序号</th> <th style="width: 70px; text-align:center; vertical-align: middle;">序号</th>
<th style="width: 280px; text-align:center; vertical-align: middle;">工作事项</th> <th style="width: 300px; text-align:center; vertical-align: middle;">工作事项</th>
<th style="text-align:center; vertical-align: middle;">完成时间</th> <th style="width: 100px; text-align:center; vertical-align: middle;">完成时间</th>
<th style="text-align:center; vertical-align: middle;">工作要求及交付物</th> <th style="width: 240px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
<th style="text-align:center; vertical-align: middle; width: 120px">承办单位</th> <th style="width: 140px; text-align:center; vertical-align: middle;">承办单位</th>
<th style="width: 120px;text-align:center; vertical-align: middle;">承/督办人</th> <th style="width: 200px;text-align:center; vertical-align: middle;">承/督办人</th>
<th style="width: 200px;text-align:center; vertical-align: middle;">协办人</th> {# <th style="width: 200px;text-align:center; vertical-align: middle;">协办人</th>#}
<th style="width: 80px; text-align:center; vertical-align: middle;">工作量(pre)</th> <th style="width: 80px; text-align:center; vertical-align: middle;">工作量(pre)</th>
<th style="width: 60px; text-align:center; vertical-align: middle;">折算系数</th> <th style="width: 60px; text-align:center; vertical-align: middle;">折算系数</th>
<th style="width: 80px; text-align:center; vertical-align: middle;">工作量</th> <th style="width: 80px; text-align:center; vertical-align: middle;">工作量</th>
<th style="width: 100px; text-align:center; vertical-align: middle;">成熟度</th> <th style="width: 100px; text-align:center; vertical-align: middle;">成熟度</th>
<th style="text-align:center; vertical-align: middle;">完成情况</th> <th style="width: 320px; text-align:center; vertical-align: middle;">完成情况</th>
<th style="width: 120px; text-align:center; vertical-align: middle;">完成质量</th> <th style="width: 120px; text-align:center; vertical-align: middle;">完成质量</th>
<th style="text-align:center; vertical-align: middle;"></th> <th style="text-align:center; vertical-align: middle;"></th>
{# <th style="width: 40px">Label</th>#} {# <th style="width: 40px">Label</th>#}
@ -132,13 +132,13 @@
<tbody> <tbody>
{% for todo in my_sub_todo %} {% for todo in my_sub_todo %}
<tr> <tr>
<td style="text-align: center">{{ todo.id }}</td> <td style="text-align: center">{{ forloop.counter }}</td>
<td>{{ todo.todo_topic }}</td> <td>{{ todo.todo_topic }}</td>
<td style="text-align: center">{{ todo.deadline | date:"m月d日" }}</td> <td style="text-align: center">{{ todo.deadline | date:"m月d日" }}</td>
<td style="width: 160px"><textarea style="width:100%;overflow:hidden">{{ todo.todo_note }}</textarea></td> <td><textarea style="width:100%;overflow:hidden">{{ todo.todo_note }}</textarea></td>
<td style="text-align: center">{{ todo.duty_group }}</td> <td style="text-align: center">{{ todo.duty_group }}</td>
<td style="text-align: center">{{ todo.main_executor.all|join:", " }}</td> <td style="text-align: center">{{ todo.main_executor }}</td>
<td style="text-align: center">{{ todo.sub_executor.all|join:", " }}</td> {# <td style="text-align: center">{{ todo.sub_executor.all|join:", " }}</td>#}
<td style="text-align:center;">{{ todo.predict_work }}</td> <td style="text-align:center;">{{ todo.predict_work }}</td>
<td style="text-align:center;">{{ todo.evaluate_factor }}</td> <td style="text-align:center;">{{ todo.evaluate_factor }}</td>
<td style="text-align:center;">{{ todo.real_work|default:'' }}</td> <td style="text-align:center;">{{ todo.real_work|default:'' }}</td>
@ -148,9 +148,9 @@
</div> </div>
<span class="badge bg-gradient-blue">{{ todo.maturity }}</span> <span class="badge bg-gradient-blue">{{ todo.maturity }}</span>
</td> </td>
<td style="text-align: center"><textarea style="overflow:hidden; width: 300px">{{ todo.complete_note }}</textarea></td> <td style="text-align: center"><textarea style="overflow:hidden; width:100%" readonly="readonly">{{ todo.complete_note }}</textarea></td>
<td style="text-align: center">{{ todo.quality_mark|default:'' }}</td> <td style="text-align: center">{{ todo.quality_mark|default:'' }}</td>
<td><a href="{% url 'tasks:todo_detail' pk=todo.id %}">填报</a> </td> <td><a href="{% url 'tasks:todo_detail' pk=todo.id %}?next={{ current_path }}">填报</a> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

1552
yarn.lock Normal file

File diff suppressed because it is too large Load Diff