修改抽签算法,降低随机性
This commit is contained in:
parent
d354a50554
commit
46393fa443
|
@ -1,3 +1,4 @@
|
||||||
/.idea/
|
/.idea/
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
*.ini
|
||||||
|
|
78
src/dao.py
78
src/dao.py
|
@ -2,7 +2,7 @@ import sqlite3
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
from flask import request
|
from utils import get_user
|
||||||
|
|
||||||
db_path = './data.sqlite3'
|
db_path = './data.sqlite3'
|
||||||
|
|
||||||
|
@ -40,16 +40,7 @@ finally:
|
||||||
ddl_db.close()
|
ddl_db.close()
|
||||||
|
|
||||||
|
|
||||||
def get_user() -> str:
|
def get_user_menu() -> tuple[str, str]:
|
||||||
"""
|
|
||||||
根据访问IP决定用户ID
|
|
||||||
:return: 用户ID
|
|
||||||
"""
|
|
||||||
client_ip = request.remote_addr
|
|
||||||
return client_ip
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_menu() -> str:
|
|
||||||
"""
|
"""
|
||||||
获取当前用户的投票内容
|
获取当前用户的投票内容
|
||||||
:return: 投票内容
|
:return: 投票内容
|
||||||
|
@ -59,25 +50,26 @@ def get_user_menu() -> str:
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
datestr = datetime.now().strftime('%Y-%m-%d')
|
datestr = datetime.now().strftime('%Y-%m-%d')
|
||||||
try:
|
try:
|
||||||
cursor.execute("select menu from user_menu where user = ? and datestr=?", (user, datestr))
|
cursor.execute("select menu, nickname from user_menu where user = ? and datestr=?", (user, datestr))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row:
|
if row:
|
||||||
return row[0]
|
return row[0], row[1]
|
||||||
# else:
|
# else:
|
||||||
# cursor.execute("select menu from user_menu where user = ? order by datestr desc limit 1", (user,))
|
# cursor.execute("select menu from user_menu where user = ? order by datestr desc limit 1", (user,))
|
||||||
# row = cursor.fetchone()
|
# row = cursor.fetchone()
|
||||||
# if row:
|
# if row:
|
||||||
# return row[0]
|
# return row[0]
|
||||||
return ''
|
return '', ''
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def set_user_menu(menu: str, user: str = None) -> None:
|
def set_user_menu(menu: str, nickname: str, user: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
设置用户投票内容
|
设置用户投票内容
|
||||||
:param menu: 投票内容
|
:param menu: 投票内容
|
||||||
|
:param nickname: 姓名
|
||||||
:param user: 用户,默认当前用户
|
:param user: 用户,默认当前用户
|
||||||
"""
|
"""
|
||||||
datestr = datetime.now().strftime('%Y-%m-%d')
|
datestr = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
@ -86,8 +78,8 @@ def set_user_menu(menu: str, user: str = None) -> None:
|
||||||
db = sqlite3.connect(db_path)
|
db = sqlite3.connect(db_path)
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
try:
|
try:
|
||||||
cursor.execute("insert or replace into user_menu(user,menu,datestr) values(?,?,?)",
|
cursor.execute("insert or replace into user_menu(user,menu,datestr,nickname) values(?,?,?,?)",
|
||||||
(user, menu, datestr))
|
(user, menu, datestr, nickname))
|
||||||
db.commit()
|
db.commit()
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
@ -102,7 +94,7 @@ def fetch_all_user_today_menu() -> Generator[tuple[str, str], None, None]:
|
||||||
db = sqlite3.connect(db_path)
|
db = sqlite3.connect(db_path)
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
try:
|
try:
|
||||||
cursor.execute("select user, menu from user_menu where menu != '' and datestr=? order by user",
|
cursor.execute("select nickname, menu from user_menu where menu != '' and datestr=? order by user",
|
||||||
(datestr,))
|
(datestr,))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
while row:
|
while row:
|
||||||
|
@ -113,6 +105,24 @@ def fetch_all_user_today_menu() -> Generator[tuple[str, str], None, None]:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_user(nickname) -> bool:
|
||||||
|
"""
|
||||||
|
判断是否是白名单姓名
|
||||||
|
"""
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cursor = db.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("select 1 from users where nickname = ?",
|
||||||
|
(nickname,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def fetch_all_menu() -> Generator[tuple[str, str, str], None, None]:
|
def fetch_all_menu() -> Generator[tuple[str, str, str], None, None]:
|
||||||
"""
|
"""
|
||||||
获取所有可点的菜单
|
获取所有可点的菜单
|
||||||
|
@ -130,13 +140,12 @@ def fetch_all_menu() -> Generator[tuple[str, str, str], None, None]:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def fetch_roll_result(interval: int = 0) -> str | None:
|
def fetch_roll_result() -> str|None:
|
||||||
"""
|
"""
|
||||||
获取N天前的抽签结果
|
获取N天前的抽签结果
|
||||||
:param interval: 间隔,天
|
|
||||||
:return: 抽签结果
|
:return: 抽签结果
|
||||||
"""
|
"""
|
||||||
date = datetime.now() + timedelta(days=interval)
|
date = datetime.now()
|
||||||
datestr = date.strftime('%Y-%m-%d')
|
datestr = date.strftime('%Y-%m-%d')
|
||||||
db = sqlite3.connect(db_path)
|
db = sqlite3.connect(db_path)
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
|
@ -144,7 +153,7 @@ def fetch_roll_result(interval: int = 0) -> str | None:
|
||||||
cursor.execute("select value from roll_result where datestr=?",
|
cursor.execute("select value from roll_result where datestr=?",
|
||||||
(datestr, ))
|
(datestr, ))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row:
|
if row is not None:
|
||||||
return row[0]
|
return row[0]
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
|
@ -152,6 +161,31 @@ def fetch_roll_result(interval: int = 0) -> str | None:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_roll_result_list(interval: int = 0, limit: int = 3) -> Generator[str, None, None]:
|
||||||
|
"""
|
||||||
|
获取N天前的抽签结果
|
||||||
|
:param interval: 间隔,天
|
||||||
|
:param limit: 最多取X条
|
||||||
|
:return: 抽签结果
|
||||||
|
"""
|
||||||
|
date = datetime.now() + timedelta(days=interval)
|
||||||
|
datestr = date.strftime('%Y-%m-%d')
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cursor = db.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("select value from roll_result where datestr<=? order by datestr desc limit ?",
|
||||||
|
(datestr, limit))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
while row is not None:
|
||||||
|
yield row[0]
|
||||||
|
if interval != 0:
|
||||||
|
return
|
||||||
|
row = cursor.fetchone()
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def set_roll_result(value: str) -> None:
|
def set_roll_result(value: str) -> None:
|
||||||
"""
|
"""
|
||||||
写入抽签结果
|
写入抽签结果
|
||||||
|
|
|
@ -1,31 +1,14 @@
|
||||||
import json
|
import json
|
||||||
from re import Pattern
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from session import app
|
from session import app
|
||||||
from flask import render_template, make_response, abort
|
from flask import render_template, make_response, abort, request
|
||||||
import re
|
|
||||||
import random
|
import random
|
||||||
from dao import *
|
from dao import *
|
||||||
|
from utils import is_mobile_request
|
||||||
MOBILE_AGENTS_PATTERN: Pattern[str] = re.compile(
|
|
||||||
r"(android|bb\d+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|"
|
|
||||||
r"ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|"
|
|
||||||
r"pocket|psp|series([46])0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino",
|
|
||||||
re.IGNORECASE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_mobile_request(user_agent: str) -> bool:
|
def fetch_user_menu_summary() -> tuple[dict[str, float], list[str]]:
|
||||||
"""
|
|
||||||
判断是否是移动端
|
|
||||||
:param user_agent: UA
|
|
||||||
:return: 是否
|
|
||||||
"""
|
|
||||||
return bool(MOBILE_AGENTS_PATTERN.search(user_agent))
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_user_menu_summary() -> dict[str, float]:
|
|
||||||
"""
|
"""
|
||||||
获取今天所有人的投票汇总
|
获取今天所有人的投票汇总
|
||||||
:return: 品类->数量
|
:return: 品类->数量
|
||||||
|
@ -33,6 +16,7 @@ def fetch_user_menu_summary() -> dict[str, float]:
|
||||||
all_menu = list(fetch_all_user_today_menu())
|
all_menu = list(fetch_all_user_today_menu())
|
||||||
if len(all_menu) > 0:
|
if len(all_menu) > 0:
|
||||||
menus = list(map(lambda x: json.loads(x[1]), all_menu))
|
menus = list(map(lambda x: json.loads(x[1]), all_menu))
|
||||||
|
users = list(map(lambda x: x[0], all_menu))
|
||||||
menu_keys = set(chain(*menus))
|
menu_keys = set(chain(*menus))
|
||||||
result = {}
|
result = {}
|
||||||
for k in menu_keys:
|
for k in menu_keys:
|
||||||
|
@ -40,8 +24,8 @@ def fetch_user_menu_summary() -> dict[str, float]:
|
||||||
for user_menu in menus:
|
for user_menu in menus:
|
||||||
for k in user_menu:
|
for k in user_menu:
|
||||||
result[k] += user_menu[k]
|
result[k] += user_menu[k]
|
||||||
return result
|
return result, users
|
||||||
return {}
|
return {}, []
|
||||||
|
|
||||||
|
|
||||||
def within_time() -> bool:
|
def within_time() -> bool:
|
||||||
|
@ -67,13 +51,13 @@ def check_roll() -> int:
|
||||||
return 0 if within_time() else 1
|
return 0 if within_time() else 1
|
||||||
|
|
||||||
|
|
||||||
def vote_reduce(summary: dict[str, float]) -> tuple[dict[str, float], float, str]:
|
def vote_reduce(summary: dict[str, float], limit: int = 2) -> tuple[dict[str, float], float, list[str]]:
|
||||||
"""
|
"""
|
||||||
按规则对投票结果进行修饰
|
按规则对投票结果进行修饰
|
||||||
:param summary: 投票汇总结果
|
:param summary: 投票汇总结果
|
||||||
:return: 投票汇总结果
|
:return: 投票汇总结果
|
||||||
"""
|
"""
|
||||||
last_result = fetch_roll_result(-1)
|
last_results = list(fetch_roll_result_list(-1, limit))
|
||||||
total_vote = sum(value for value in summary.values())
|
total_vote = sum(value for value in summary.values())
|
||||||
for menu in fetch_all_menu():
|
for menu in fetch_all_menu():
|
||||||
name, _, expression = menu
|
name, _, expression = menu
|
||||||
|
@ -88,10 +72,11 @@ def vote_reduce(summary: dict[str, float]) -> tuple[dict[str, float], float, str
|
||||||
summary[new_name] += summary[name]
|
summary[new_name] += summary[name]
|
||||||
summary[name] = 0
|
summary[name] = 0
|
||||||
# 昨日中签项降低权重
|
# 昨日中签项降低权重
|
||||||
|
for i, last_result in enumerate(last_results):
|
||||||
if last_result in summary:
|
if last_result in summary:
|
||||||
summary[last_result] = summary[last_result] * 7 / 10
|
summary[last_result] = summary[last_result] * (9 - i) / 10
|
||||||
total_vote = sum(value for value in summary.values())
|
total_vote = sum(value for value in summary.values())
|
||||||
return summary, total_vote, last_result
|
return summary, total_vote, last_results
|
||||||
|
|
||||||
|
|
||||||
@app.route('/dinner/update')
|
@app.route('/dinner/update')
|
||||||
|
@ -107,19 +92,24 @@ def dinner_update():
|
||||||
if check_roll() != 0:
|
if check_roll() != 0:
|
||||||
return make_response(json.dumps(dict(code=-1, data="来晚了,提交失败")))
|
return make_response(json.dumps(dict(code=-1, data="来晚了,提交失败")))
|
||||||
user_menu = request.args.get('value').strip()
|
user_menu = request.args.get('value').strip()
|
||||||
|
nickname = request.args.get('nickname').strip()
|
||||||
|
if not nickname:
|
||||||
|
return make_response(json.dumps(dict(code=-1, data="姓名必须填写")))
|
||||||
|
if not is_valid_user(nickname):
|
||||||
|
abort(403)
|
||||||
if not user_menu:
|
if not user_menu:
|
||||||
set_user_menu('')
|
set_user_menu('', nickname)
|
||||||
return make_response(json.dumps(dict(code=0, data="OK")))
|
return make_response(json.dumps(dict(code=0, data="OK")))
|
||||||
user_menu = json.loads(user_menu)
|
user_menu = json.loads(user_menu)
|
||||||
# 计算总投票数值
|
# 计算总投票数值
|
||||||
summary = sum(abs(int(value)) for value in user_menu.values())
|
summary = sum(abs(int(value)) for value in user_menu.values())
|
||||||
if summary <= 0:
|
if summary <= 0:
|
||||||
set_user_menu('')
|
set_user_menu('', nickname)
|
||||||
return make_response(json.dumps(dict(code=0, data="OK")))
|
return make_response(json.dumps(dict(code=0, data="OK")))
|
||||||
# 投票数归一化
|
# 投票数归一化
|
||||||
for key in user_menu:
|
for key in user_menu:
|
||||||
user_menu[key] = abs(int(user_menu[key])) / summary
|
user_menu[key] = abs(int(user_menu[key])) / summary
|
||||||
set_user_menu(json.dumps(user_menu, ensure_ascii=False))
|
set_user_menu(json.dumps(user_menu, ensure_ascii=False), nickname)
|
||||||
return make_response(json.dumps(dict(code=0, data="OK")))
|
return make_response(json.dumps(dict(code=0, data="OK")))
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,38 +121,57 @@ def dinner_roll():
|
||||||
"""
|
"""
|
||||||
if check_roll() != 1:
|
if check_roll() != 1:
|
||||||
return make_response(json.dumps(dict(code=-1, data="目前不能抽签")))
|
return make_response(json.dumps(dict(code=-1, data="目前不能抽签")))
|
||||||
summary, _, _ = vote_reduce(fetch_user_menu_summary())
|
result = roll_logic()
|
||||||
# 票数乘以100四舍五入取整,投入抽奖池
|
|
||||||
pool = list(chain(*[[name] * int(round(summary[name] * 100)) for name in summary]))
|
|
||||||
# Knuth-Durstenfeld Shuffle算法洗牌: 从后往前依次随机将未乱序元素交换到当前位置,直到所有元素均被打乱
|
|
||||||
random.shuffle(pool)
|
|
||||||
result = pool[0]
|
|
||||||
set_roll_result(result)
|
set_roll_result(result)
|
||||||
return make_response(json.dumps(dict(code=0, data="OK")))
|
return make_response(json.dumps(dict(code=0, data="OK")))
|
||||||
|
|
||||||
|
|
||||||
|
def roll_logic(check=False):
|
||||||
|
menus, _ = fetch_user_menu_summary()
|
||||||
|
summary, _, _ = vote_reduce(menus)
|
||||||
|
sorted_items = sorted(summary.items(), key=lambda item: item[1], reverse=True)
|
||||||
|
items = [item for item in sorted_items[:2]]
|
||||||
|
if len(items) > 1 and (items[0][1] * 0.9) <= items[1][1]:
|
||||||
|
if check:
|
||||||
|
return None
|
||||||
|
# 票数乘以100四舍五入取整,投入抽奖池
|
||||||
|
pool = list(chain(*[[name] * int(round(value * 100)) for name, value in items]))
|
||||||
|
result = pool[random.randint(0, len(pool))]
|
||||||
|
return result
|
||||||
|
elif len(items) > 0:
|
||||||
|
return items[0][0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@app.route('/dinner')
|
@app.route('/dinner')
|
||||||
def dinner():
|
def dinner():
|
||||||
"""
|
"""
|
||||||
主页面
|
主页面
|
||||||
:return: 响应
|
:return: 响应
|
||||||
"""
|
"""
|
||||||
menu = get_user_menu()
|
menu, nickname = get_user_menu()
|
||||||
if menu:
|
if menu:
|
||||||
menu = json.loads(menu)
|
menu = json.loads(menu)
|
||||||
else:
|
else:
|
||||||
menu = {}
|
menu = {}
|
||||||
summary, total_vote, last_result = vote_reduce(fetch_user_menu_summary())
|
menus, users = fetch_user_menu_summary()
|
||||||
|
summary, total_vote, last_results = vote_reduce(menus)
|
||||||
result = fetch_roll_result()
|
result = fetch_roll_result()
|
||||||
can_roll = (check_roll() == 1)
|
can_roll = (check_roll() == 1)
|
||||||
all_choice = list(map(lambda x: {'name': x[0], 'label': x[1]}, fetch_all_menu()))
|
all_choice = list(map(lambda x: {'name': x[0], 'label': x[1]}, fetch_all_menu()))
|
||||||
summary_keys = list(filter(lambda x: x in summary.keys(), map(lambda y: y['name'], all_choice)))
|
summary_keys = list(filter(lambda x: x in summary.keys(), map(lambda y: y['name'], all_choice)))
|
||||||
|
if not result:
|
||||||
|
predict_result = roll_logic(check=True)
|
||||||
return render_template('dinner.html',
|
return render_template('dinner.html',
|
||||||
all_choice=all_choice,
|
all_choice=all_choice,
|
||||||
menu=menu,
|
menu=menu,
|
||||||
|
nickname=nickname,
|
||||||
|
users=users,
|
||||||
summary=summary,
|
summary=summary,
|
||||||
summary_keys=summary_keys,
|
summary_keys=summary_keys,
|
||||||
total_vote=total_vote,
|
total_vote=total_vote,
|
||||||
result=result,
|
result=result,
|
||||||
|
predict_result=predict_result,
|
||||||
can_roll=can_roll,
|
can_roll=can_roll,
|
||||||
last_result=last_result)
|
last_results=last_results)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import re
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
|
||||||
|
config = ConfigParser()
|
||||||
|
config.read('./config.ini')
|
||||||
|
|
||||||
|
def is_mobile_request(user_agent: str) -> bool:
|
||||||
|
"""
|
||||||
|
判断是否是移动端
|
||||||
|
:param user_agent: UA
|
||||||
|
:return: 是否
|
||||||
|
"""
|
||||||
|
ua_expression = config.get('Settings', 'UA_EXPRESSION')
|
||||||
|
return bool(eval(ua_expression, {"user_agent": user_agent, "re": re}))
|
||||||
|
|
||||||
|
def get_user() -> str:
|
||||||
|
"""
|
||||||
|
根据访问IP决定用户ID
|
||||||
|
:return: 用户ID
|
||||||
|
"""
|
||||||
|
client_ip = request.remote_addr
|
||||||
|
return client_ip
|
|
@ -5,22 +5,28 @@
|
||||||
<link rel="stylesheet" href="/static/bootstrap.min.css">
|
<link rel="stylesheet" href="/static/bootstrap.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container text-center" style="padding-top: 120px">
|
<div class="container text-center pt-5">
|
||||||
<p>每天8:00-17:30间开放匿名投票更新,17:30以后允许发起抽签,抽签结果确定后不可更改</p>
|
<p>每天8:00-17:30间开放匿名投票更新,17:30以后允许发起抽签,抽签结果确定后不可更改</p>
|
||||||
<p>随机抽签,按得票数决定中签概率</p>
|
<p>第一名与第二名得票数相差不超过10%时随机抽签,按其得票数决定中签概率,否则选择第一名</p>
|
||||||
{% if last_result %}
|
{% for last_result in last_results %}
|
||||||
<p>今日{{ last_result }}最终得票数降低30%</p>
|
<p>今日{{ last_result }}最终得票数降低{{ 1 + loop.index0 }}0%</p>
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
<form id="inputForm" class="form-inline">
|
<form id="inputForm" class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group">
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text" id="basic-addon1">姓名</span>
|
||||||
|
<input type="text" id="inputName" class="form-control"
|
||||||
|
name="nickname"
|
||||||
|
value="{{ nickname }}">
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-2">
|
||||||
<span class="input-group-addon">我选择</span>
|
<span class="input-group-addon">我选择</span>
|
||||||
</div>
|
</div>
|
||||||
{% for choice in all_choice %}
|
{% for choice in all_choice %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="range{{ loop.index }}" class="form-label">
|
<label for="range{{ loop.index }}" class="form-label">
|
||||||
<span>{{ choice['label'] }}</span> - <span
|
<span>{{ choice['label'] }} - </span>
|
||||||
class="percentage">{{ '{:.2f}'.format((menu.get(choice['name']) or 0) * 100) }}</span>%
|
<span class="percentage">{{ '{:.2f}'.format((menu.get(choice['name']) or 0) * 100) }}</span>%
|
||||||
</label>
|
</label>
|
||||||
<input type="range" class="form-range"
|
<input type="range" class="form-range"
|
||||||
id="range{{ loop.index }}" name="{{ choice['name'] }}"
|
id="range{{ loop.index }}" name="{{ choice['name'] }}"
|
||||||
|
@ -36,11 +42,11 @@
|
||||||
class="btn btn-secondary" {{ "" if can_roll == True else 'disabled="disabled"' | safe }}
|
class="btn btn-secondary" {{ "" if can_roll == True else 'disabled="disabled"' | safe }}
|
||||||
onclick="roll()">开始抽签
|
onclick="roll()">开始抽签
|
||||||
</button>
|
</button>
|
||||||
<div style="padding-top: 40px">
|
<div class="pt-3">
|
||||||
<label>大家的选择 - 总票数:{{ '{:.2f}'.format(total_vote | round(2)) }}</label>
|
<label>大家的选择 - 总票数:{{ '{:.2f}'.format(total_vote | round(2)) }}</label>
|
||||||
<ul style="padding-top: 20px; margin-bottom: 80px" class="list-group">
|
<ul class="list-group pt-2 mb-4">
|
||||||
{% for key in summary_keys %}
|
{% for key in summary_keys %}
|
||||||
<li class="list-group-item {{ "active" if result == key else "" }}">
|
<li class="list-group-item {{ "active" if result == key else "" }} {{ "list-group-item-primary" if predict_result == key else "" }}">
|
||||||
<div class="row justify-content-between">
|
<div class="row justify-content-between">
|
||||||
<span class="col-5">{{ key }}</span>
|
<span class="col-5">{{ key }}</span>
|
||||||
<span class="col-3">{{ '{:.2f}'.format(summary[key] | round(2)) }}票</span>
|
<span class="col-3">{{ '{:.2f}'.format(summary[key] | round(2)) }}票</span>
|
||||||
|
@ -50,11 +56,32 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="fixed-bottom" style="padding-bottom: 20px"><a class="link-secondary"
|
{% if (users|length) > 3 %}
|
||||||
style="text-decoration: none; font-size: 0.8rem"
|
<div class="pt-1 mb-3">
|
||||||
|
<label>参与投票人员</label>
|
||||||
|
<ul class="list-group list-group-horizontal">
|
||||||
|
{% for user in users %}
|
||||||
|
<li class="list-group-item">{{ user }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<footer class="footer mt-auto py-3 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<a class="link-secondary link-offset-2"
|
||||||
href="https://git.zaneyork.cn:8443/ZaneYork/dinner_vote">本项目抽签完全公开透明,源码开放欢迎随时审查</a>
|
href="https://git.zaneyork.cn:8443/ZaneYork/dinner_vote">本项目抽签完全公开透明,源码开放欢迎随时审查</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<span style="font-size: 0.7rem">Copyright © <script>document.write(new Date().getFullYear().toString())</script> Zane York. All Rights Reserved.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/static/jquery-3.2.1.min.js"></script>
|
<script src="/static/jquery-3.2.1.min.js"></script>
|
||||||
<script src="/static/jquery.serializejson.js"></script>
|
<script src="/static/jquery.serializejson.js"></script>
|
||||||
|
@ -69,14 +96,16 @@
|
||||||
const summary = values.reduce((a, b) => parseInt(a) + parseInt(b));
|
const summary = values.reduce((a, b) => parseInt(a) + parseInt(b));
|
||||||
const spans = $(".percentage")
|
const spans = $(".percentage")
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
spans[i].innerHTML = (parseFloat(values[i]) * 100 / summary).toFixed(2);
|
spans[i].innerHTML = summary > 0 ? (parseFloat(values[i]) * 100 / summary).toFixed(2) : 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
const data = $('#inputForm').serializeJSON();
|
const data = $('#inputForm').serializeJSON();
|
||||||
|
const nickname = data.nickname;
|
||||||
|
data.nickname = undefined;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'dinner/update?value=' + JSON.stringify(data),
|
url: 'dinner/update?nickname=' + nickname + '&value=' + JSON.stringify(data),
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (result) {
|
success: function (result) {
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
|
|
Loading…
Reference in New Issue