Compare commits

...

40 Commits
v0.6 ... 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
53 changed files with 32312 additions and 16274 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

4
.gitignore vendored
View File

@ -4,3 +4,7 @@
/db.sqlite3 /db.sqlite3
/identifier.sqlite /identifier.sqlite
migrations/ 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

@ -26,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 = ['192.168.31.198', '127.0.0.1', '172.20.22.40'] ALLOWED_HOSTS = ['192.168.31.198', '127.0.0.1', '172.20.22.40', '110.42.209.79']
# Application definition # Application definition
@ -39,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',
] ]
@ -135,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 配置
# 离线模式 # 离线模式
@ -146,7 +150,7 @@ 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

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,10 +1,15 @@
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, User from apps.users.models import TaskProperty, User
from .resources import TodoResources, TaskResources
class TodoInline(admin.StackedInline): class TodoInline(admin.StackedInline):
@ -24,9 +29,11 @@ class TodoInline(admin.StackedInline):
model = models.Todo model = models.Todo
extra = 0 extra = 0
# classes = ['collapse'] # classes = ['collapse']
#TODO 选择年度任务时排序 https://www.codenong.com/40740869/
class TaskAdmin(admin.ModelAdmin): 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":
@ -57,6 +64,18 @@ class TaskAdmin(admin.ModelAdmin):
else: else:
return qs.filter(department=request.user.department) 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',
@ -76,8 +95,23 @@ class TaskAdmin(admin.ModelAdmin):
# autocomplete_fields = ('related_task',) # autocomplete_fields = ('related_task',)
# search_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()]
class TodoAdmin(admin.ModelAdmin): def has_import_permission(self, request): # 这是隐藏导入按钮,如果隐藏其他按钮也可以这样操作,
if request.user.is_superuser:
return True
else:
return False
# class TodoAdmin(admin.ModelAdmin):
class TodoAdmin(ImportExportModelAdmin):
resource_class = TodoResources
# 工作包页面仅显示所属本部门的年度任务、承办人、协办人 # 工作包页面仅显示所属本部门的年度任务、承办人、协办人
def formfield_for_foreignkey(self, db_field, request, **kwargs): def formfield_for_foreignkey(self, db_field, request, **kwargs):
@ -104,9 +138,9 @@ class TodoAdmin(admin.ModelAdmin):
(None, { (None, {
'fields': [ 'fields': [
'related_task', 'todo_topic', 'todo_note', 'deadline', 'duty_group', 'main_executor', 'sub_executor', 'related_task', 'todo_topic', 'todo_note', 'deadline', 'duty_group', 'main_executor', 'sub_executor',
'predict_work', 'evaluate_factor', 'sub_executor_count', 'predict_work', 'evaluate_factor', 'real_work'
], ],
'description': 'aaa' 'description': ''
}), }),
(None, { (None, {
@ -124,6 +158,8 @@ class TodoAdmin(admin.ModelAdmin):
'duty_group', 'duty_group',
'main_executor', 'main_executor',
'list_sub_executor', 'list_sub_executor',
'predict_work',
'evaluate_factor',
'maturity', 'maturity',
'real_work', 'real_work',
'complete_note', 'complete_note',
@ -137,6 +173,7 @@ class TodoAdmin(admin.ModelAdmin):
# raw_id_fields = ("sub_executor",) # raw_id_fields = ("sub_executor",)
search_fields = ('todo_topic',) search_fields = ('todo_topic',)
ordering = ('related_task', ) 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)
@ -158,7 +195,181 @@ class TodoAdmin(admin.ModelAdmin):
return obj.related_task return obj.related_task
lined_task.short_description = '任务名称' lined_task.short_description = '任务名称'
# TODO 任务编辑界面按部门显示 # 导入导出功能限制
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)

View File

@ -1,20 +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' 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', 'real_work', '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 = {'rows': '3'} # 'evaluate_factor': forms.NumberInput(attrs={'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
# TODO 数据不可为空 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,56 +0,0 @@
# Generated by Django 3.1.5 on 2021-03-05 19:27
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', '0003_auto_20210304_2224'),
]
operations = [
migrations.AlterField(
model_name='task',
name='aim_value',
field=models.CharField(blank=True, max_length=50, verbose_name='目标值'),
),
migrations.AlterField(
model_name='task',
name='leader',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='leader', to=settings.AUTH_USER_MODEL, verbose_name='主管领导'),
),
migrations.AlterField(
model_name='task',
name='principal',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='principal', to=settings.AUTH_USER_MODEL, verbose_name='负责人'),
),
migrations.AlterField(
model_name='task',
name='task_note',
field=models.CharField(blank=True, max_length=100, verbose_name='任务说明'),
),
migrations.AlterField(
model_name='task',
name='task_origin',
field=models.CharField(blank=True, max_length=150, verbose_name='任务来源'),
),
migrations.AlterField(
model_name='todo',
name='evaluate_factor',
field=models.DecimalField(blank=True, decimal_places=1, default='1', max_digits=5, verbose_name='折算系数'),
),
migrations.AlterField(
model_name='todo',
name='predict_work',
field=models.DecimalField(blank=True, decimal_places=1, max_digits=5, verbose_name='预计工作量'),
),
migrations.AlterField(
model_name='todo',
name='related_task',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_task', to='tasks.task', verbose_name='年度任务'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.1.5 on 2021-03-05 20:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0004_auto_20210305_1927'),
]
operations = [
migrations.AlterField(
model_name='task',
name='task_id',
field=models.CharField(max_length=50, unique=True, verbose_name='编号'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.1.5 on 2021-03-05 21:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0005_auto_20210305_2059'),
]
operations = [
migrations.AlterField(
model_name='todo',
name='predict_work',
field=models.DecimalField(blank=True, decimal_places=1, max_digits=5, null=True, verbose_name='预计工作量'),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 3.1.5 on 2021-03-05 22:25
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', '0006_auto_20210305_2106'),
]
operations = [
migrations.AlterField(
model_name='task',
name='leader',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leader', to=settings.AUTH_USER_MODEL, verbose_name='主管领导'),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 3.1.5 on 2021-03-05 22:26
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', '0007_auto_20210305_2225'),
]
operations = [
migrations.AlterField(
model_name='task',
name='principal',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='principal', to=settings.AUTH_USER_MODEL, verbose_name='负责人'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-03-05 23:17
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('tasks', '0008_auto_20210305_2226'),
]
operations = [
migrations.AlterField(
model_name='todo',
name='sub_executor',
field=models.ManyToManyField(blank=True, related_name='sub_executor', to=settings.AUTH_USER_MODEL, verbose_name='协办人'),
),
]

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
@ -18,9 +22,10 @@ class Todo(models.Model):
main_executor = models.ForeignKey(User, related_name='main_executor', on_delete=models.CASCADE, main_executor = models.ForeignKey(User, related_name='main_executor', on_delete=models.CASCADE,
verbose_name='承/督办人', blank=True, null=True) verbose_name='承/督办人', blank=True, null=True)
sub_executor = models.ManyToManyField(User, related_name='sub_executor', verbose_name='协办人', blank=True) sub_executor = models.ManyToManyField(User, related_name='sub_executor', verbose_name='协办人', blank=True)
sub_executor_count = models.CharField('协办人数', max_length=32)
related_task = models.ForeignKey('Task', related_name='related_task', on_delete=models.CASCADE, verbose_name='年度任务') related_task = models.ForeignKey('Task', related_name='related_task', on_delete=models.CASCADE, verbose_name='年度任务')
predict_work = models.DecimalField('预计工作量', max_digits=5, decimal_places=1, blank=True, null=True) 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') 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,
@ -28,17 +33,17 @@ class Todo(models.Model):
('0%', '0%'), ('0%', '0%'),
('10%', '10%'), ('10%', '10%'),
('50%', '50%'), ('50%', '50%'),
('70%', '70%'),
('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, quality_mark = models.ForeignKey('users.QualityMark', on_delete=models.SET_NULL, blank=True, null=True,
verbose_name='质量评价') verbose_name='质量评价')
attachment = models.FileField('交付物查看', blank=True)
def __str__(self): def __str__(self):
date = str(self.deadline) date = str(self.deadline)
@ -50,6 +55,13 @@ class Todo(models.Model):
verbose_name = '工作包' verbose_name = '工作包'
verbose_name_plural = '工作包' verbose_name_plural = '工作包'
# def save(self, *args, **kwargs):
# super(Todo, self).save(*args, **kwargs)
# print(request.HttpRequest)
# # 直接保存报错 needs to have a value for field "id" before this many-to-many relationship can be used.
# # self.sub_executor_count = int(self.sub_executor.count())
# # Todo.objects.update(sub_executor_count=self.sub_executor.count())
@property @property
def task_id(self): def task_id(self):
return self.related_task.task_id return self.related_task.task_id
@ -74,6 +86,18 @@ class Todo(models.Model):
def points(self): def points(self):
return int(self.predict_work * self.evaluate_factor) 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): def list_sub_executor(self):
return ', '.join([a.real_name for a in self.sub_executor.all()]) return ', '.join([a.real_name for a in self.sub_executor.all()])
list_sub_executor.short_description = '协办人' list_sub_executor.short_description = '协办人'

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,11 +7,18 @@ 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 str(value) + '&#13;&#10;' return str(value) + '&#13;&#10;'
@ -26,6 +33,9 @@ def quarter_cate(value, quarter):
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,5 +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'), 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,28 +1,362 @@
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 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 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):
@method_decorator(login_required) @method_decorator(login_required)
def get(self, request): def get(self, request, year=None, month=None):
users = User.objects.filter(department=request.user.department) # 搞成了!!! 当月份更改时自动变更首页显示的月份! 其实就是用的上面那个链接的方法,之前不知道为啥没去用
# points = [] # https://stackoverflow.com/questions/63072235/django-localdate-doesnt-return-correct-date
# point = User.objects.all() # https://stackoverflow.com/questions/13225890/django-default-timezone-now-saves-records-using-old-time
# for i in point: # https://stackoverflow.com/questions/38237777/django-timezone-now-vs-timezone-now
# points.append(i) if year is None:
# points = User.objects.annotate(a=F('main_executor__evaluate_factor')) year = timezone.now().year
points = User.objects.annotate(a=Sum(F('main_executor__predict_work') * F('main_executor__evaluate_factor') + F('sub_executor__predict_work') * F('sub_executor__evaluate_factor'))) if month is None:
context = {'users': users, 'points': points} 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) return render(request, 'tasks/index.html', context)
@ -32,7 +366,8 @@ 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)
@ -42,17 +377,25 @@ class GroupTodoList(View):
group_todo = Todo.objects.filter(main_executor__department=request.user.department, deadline__year=year, group_todo = Todo.objects.filter(main_executor__department=request.user.department, deadline__year=year,
deadline__month=month).order_by('related_task_id', 'deadline') 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')
@ -68,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)
@ -84,8 +428,12 @@ 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()
if url_has_allowed_host_and_scheme(redirect_to, None):
return redirect(redirect_to)
else:
return redirect('tasks:todolist') return redirect('tasks:todolist')
# return redirect('tasks:todo_detail', pk=pk) # return redirect('tasks:todo_detail', pk=pk)

View File

@ -23,14 +23,24 @@ 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',
@ -40,9 +50,27 @@ class QualityMarkAdmin(admin.ModelAdmin):
return self.mark_value return self.mark_value
mark_value.short_description = 'ss' 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

@ -9,7 +9,7 @@ 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 = '用户'
@ -45,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.3 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

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

@ -13,3 +13,8 @@
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
.col-xs-1-5 {
width: 20%;
float: left;
}

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ABOUT - Raiot</title> <title>ABOUT - Raiot</title>
<link rel="stylesheet" href="{% static 'tasks/nes.min.font.css' %}"> <link rel="stylesheet" href="{% static 'tasks/nes.min.css' %}">
<style> <style>
@charset "utf-8"; @charset "utf-8";
@import url({% static 'tasks/about_font.css' %}); @import url({% static 'tasks/about_font.css' %});
@ -142,7 +142,7 @@
<header class=""> <header class="">
<div class="container"> <div class="container">
<div class="nav-brand"> <div class="nav-brand">
<a href="https://github.com/Anankke/SSPanel-Uim"> <a href="#">
<h1>STAFF</h1> <h1>STAFF</h1>
</a> </a>
<p>©&nbsp;2021 RAIOT</p> <p>©&nbsp;2021 RAIOT</p>

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>#}
@ -191,7 +199,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
<li class="nav-tabs"> <li class="nav-tabs">
<a>欢迎,</a> <a>欢迎,</a>
<b style="color: cadetblue">{{ user.real_name }}</b> <b style="color: cadetblue">{{ user.real_name }}</b>
<a>。 本系统为非涉密应用系统,禁止处理秘密级及以上信息!!!</a> <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>

View File

@ -58,11 +58,11 @@
<table class="table" style="word-break: break-all"> <table class="table" style="word-break: break-all">
<thead> <thead>
<tr> <tr>
<th style="width: 16px; 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="width: 100px; text-align:center; vertical-align: middle;">完成时间</th> <th style="width: 100px; text-align:center; vertical-align: middle;">完成时间</th>
<th style="width: 160px; text-align:center; vertical-align: middle;">工作要求及交付物</th> <th style="width: 160px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
<th style="width: 50px; text-align:center; vertical-align: middle;">任务编号</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: 120px;text-align:center; vertical-align: middle;">承/督办人</th> <th style="width: 120px;text-align:center; vertical-align: middle;">承/督办人</th>

View File

@ -8,12 +8,20 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>总览</h1> <h1>总览 ({{ date }}) </h1>
{# <h1><font color="#FF0000">测试版,请勿用于生产环境!!!</font></h1>#}
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> {# <ol class="breadcrumb float-sm-right">#}
<li class="breadcrumb-item active"><a href="#">主页</a></li> {# <li class="breadcrumb-item active"><a href="#">主页</a></li>#}
</ol> {# </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> </div>
</div><!-- /.container-fluid --> </div><!-- /.container-fluid -->
@ -23,14 +31,13 @@
<section class="content"> <section class="content">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3"> <div class="col">
<div class="info-box"> <div class="info-box">
<span class="info-box-icon bg-info elevation-1"><i class="fas fa-tasks"></i></span> <span class="info-box-icon bg-danger elevation-1"><i class="fas fa-tasks"></i></span>
<div class="info-box-content"> <div class="info-box-content">
<span class="info-box-text">承办任务数</span> <span class="info-box-text">部门总任务数</span>
<span class="info-box-number"> <span class="info-box-number">
{{ request.user.main_executor.count }} {{ department_cal.depart_count|floatformat:'0'|default_if_none:'0' }}
<small></small> <small></small>
</span> </span>
</div> </div>
@ -39,13 +46,135 @@
<!-- /.info-box --> <!-- /.info-box -->
</div> </div>
<!-- /.col --> <!-- /.col -->
<div class="col-12 col-sm-6 col-md-3"> <div class="col">
<div class="info-box mb-3"> <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">
{{ 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"> <div class="info-box-content">
<span class="info-box-text">协办任务数</span> <span class="info-box-text">协办任务数</span>
<span class="info-box-number">{{ user.sub_executor.count }} <span class="info-box-number">
{{ current_user.sub_count }}
<small></small> <small></small>
</span> </span>
</div> </div>
@ -54,6 +183,34 @@
<!-- /.info-box --> <!-- /.info-box -->
</div> </div>
<!-- /.col --> <!-- /.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 --> <!-- fix for small devices only -->
<div class="clearfix hidden-md-up"></div> <div class="clearfix hidden-md-up"></div>
@ -124,17 +281,80 @@
<!-- /.col (MAIN) --> <!-- /.col (MAIN) -->
</div> </div>
<!-- /.row --> <!-- /.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 --> </div><!-- /.container-fluid -->
</section> </section>
<!-- /.content --> <!-- /.content -->
</div> </div>
{# <div class="content-wrapper">#} {# <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 %}#} {# {% for point in points %}#}
{# {{ point }}#} {# {{ point }}#}
{# {{ point.a }}#}
{# {% endfor %}#} {# {% endfor %}#}
{##}
{# {% for user in users %}#} {# {% for user in users %}#}
{# {{ user }}#} {# {{ user }}#}
{# {{ user.get_predict_work_count.total_predict_work }}#} {# {{ user.get_predict_work_count.total_predict_work }}#}
@ -146,7 +366,7 @@
{# {% endfor %}#} {# {% endfor %}#}
{# </br>#} {# </br>#}
{# {% endfor %}#} {# {% endfor %}#}
{##}
{# </div>#} {# </div>#}
{% endblock %} {% endblock %}
@ -160,29 +380,51 @@
* Here we will create a few charts using ChartJS * Here we will create a few charts using ChartJS
*/ */
var areaChartData = { var areaChartData = {
labels : [{% for user in users %}'{{ user }}', {% endfor %}], labels : [{% for user in users_data.values %}'{{ user.real_name }}', {% endfor %}],
datasets: [ 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 : '承办任务', label : '承办任务',
backgroundColor : 'rgba(60,141,188,0.9)', backgroundColor : 'rgba(23,162,184,0.9)',
borderColor : 'rgba(60,141,188,0.8)', borderColor : 'rgba(100,98,204,0.8)',
pointRadius : false, pointRadius : false,
pointColor : '#3b8bba', pointColor : '#3b8bba',
pointStrokeColor : 'rgba(60,141,188,1)', pointStrokeColor : 'rgba(60,141,188,1)',
pointHighlightFill : '#fff', pointHighlightFill : '#fff',
pointHighlightStroke: 'rgba(60,141,188,1)', pointHighlightStroke: 'rgba(60,141,188,1)',
data : [{% for user in users %}'{{ user.main_executor.count }}', {% endfor %}] data : [{% for user in users_data.values %}'{{ user.main_count }}', {% endfor %}]
}, },
{ {
label : '协办任务', label : '预计工作量',
backgroundColor : 'rgba(210, 214, 222, 1)', backgroundColor : 'rgba(210, 214, 222,1)',
borderColor : 'rgba(210, 214, 222, 1)', borderColor : 'rgba(210, 214, 222,1)',
pointRadius : false, pointRadius : false,
pointColor : 'rgba(210, 214, 222, 1)', pointColor : '#3b8bba',
pointStrokeColor : '#c1c7d1', pointStrokeColor : 'rgba(60,141,188,1)',
pointHighlightFill : '#fff', pointHighlightFill : '#fff',
pointHighlightStroke: 'rgba(220,220,220,1)', pointHighlightStroke: 'rgba(60,141,188,1)',
data : [{% for user in users %}'{{ user.sub_executor.count }}', {% endfor %}] 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 %}]
}, },
] ]
} }

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>任务管理系统</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,7 +16,7 @@
<!-- 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">

View File

@ -33,6 +33,19 @@
<!-- /.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: 140px; text-align:center; vertical-align: middle;">任务属性</th> <th style="width: 140px; text-align:center; vertical-align: middle;">任务属性</th>
@ -80,16 +93,16 @@
<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'|safe }}{% 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'|safe }}{% 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'|safe }}{% 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'|safe }}{% 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 style="text-align:center;">{{ 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>
@ -120,5 +133,35 @@
}).on('input', function () { }).on('input', function () {
setHeight(this); setHeight(this);
}); });
</script> </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">
@ -37,11 +37,15 @@
<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.todo_note }}</dd>
<dt class="col-sm-4">完成时间</dt> <dt class="col-sm-4">完成时间</dt>
<dd class="col-sm-8">{{ todo_detail.deadline }}</dd> <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>#} {# <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.todo_note }}</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>
@ -50,6 +54,12 @@
<dd class="col-sm-8">{{ todo_detail.sub_executor.all|join:', ' }}</dd> <dd class="col-sm-8">{{ todo_detail.sub_executor.all|join:', ' }}</dd>
<dt class="col-sm-4">预计工作量</dt> <dt class="col-sm-4">预计工作量</dt>
<dd class="col-sm-8">{{ todo_detail.predict_work }}</dd> <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 -->
@ -66,14 +76,34 @@
<!-- 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"> <div class="form-group">
<label for="exampleInputEmail1">实际工作量</label> <label for="exampleInputEmail1">实际工作量</label>
{{ form.real_work }} {{ form.real_work }}
</div> </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,7 +61,7 @@
<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: 70px; text-align:center; vertical-align: middle;">序号</th> <th style="width: 100px; text-align:center; vertical-align: middle;">序号</th>
<th style="width: 300px; text-align:center; vertical-align: middle;">工作事项</th> <th style="width: 300px; 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: 240px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
@ -97,7 +97,7 @@
</td> </td>
<td style="text-align: center"><textarea style="overflow:hidden; width: 100%" readonly="readonly">{{ 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>
@ -150,7 +150,7 @@
</td> </td>
<td style="text-align: center"><textarea style="overflow:hidden; width:100%" readonly="readonly">{{ 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