Compare commits
2 Commits
f6792cad39
...
0fb7ee2992
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fb7ee2992 | ||
|
|
2bb99feda4 |
@@ -61,7 +61,7 @@ def admin_required(f):
|
||||
return decorated
|
||||
|
||||
|
||||
ALL_TENANTS = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "无界·无界"]
|
||||
ALL_TENANTS = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"]
|
||||
|
||||
@app.route("/login")
|
||||
def login_page():
|
||||
@@ -84,7 +84,7 @@ def auth_login():
|
||||
session["role"] = user["role"]
|
||||
# 管理员可看所有工作台,OPC负责人看分配的工作台
|
||||
if user["role"] == "admin":
|
||||
session["tenants"] = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "无界·无界"]
|
||||
session["tenants"] = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"]
|
||||
else:
|
||||
ut = rows(conn, "SELECT tenant FROM user_tenants WHERE user_id=?", (user["id"],))
|
||||
session["tenants"] = [x["tenant"] for x in ut]
|
||||
@@ -487,7 +487,7 @@ def init_db():
|
||||
_exec(conn, """INSERT INTO users (username, password_hash, display_name, role, created_at) VALUES (?,?,?,?,?)""",
|
||||
("wuji", generate_password_hash("wuji123", "pbkdf2:sha256"), "无界负责人", "opc_owner", date.today().isoformat()))
|
||||
# 各 OPC 负责人绑定工作台
|
||||
for uname, tenant in [("kepu","科普·无界"),("keyan","科研·无界"),("yihuan","医患·无界"),("mcn","MCN·无界"),("wuji","无界·无界")]:
|
||||
for uname, tenant in [("kepu","科普·无界"),("keyan","科研·无界"),("yihuan","医患·无界"),("mcn","MCN·无界"),("wuji","学会·无界")]:
|
||||
u = one(conn, "SELECT id FROM users WHERE username=?", (uname,))
|
||||
if u:
|
||||
_exec(conn, "INSERT INTO user_tenants (user_id, tenant) VALUES (?,?)", (u["id"], tenant))
|
||||
@@ -1049,8 +1049,9 @@ def health():
|
||||
return jsonify({"ok": True, "service": "opc-manager"})
|
||||
|
||||
|
||||
init_db()
|
||||
seed_db()
|
||||
from migrations import run_migrations
|
||||
|
||||
run_migrations()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
28
backend/migrations/__init__.py
Normal file
28
backend/migrations/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""migrations/__init__.py — 数据库自愈机制入口
|
||||
|
||||
应用启动时调用 run_migrations(),自动:
|
||||
1. 建表(CREATE TABLE IF NOT EXISTS)
|
||||
2. 加列(SHOW COLUMNS 检查后 ALTER TABLE ADD COLUMN)
|
||||
3. 数据修正(UPDATE 修复脏数据/变更枚举值)
|
||||
4. 初始化默认用户和示例数据(仅空库时)
|
||||
|
||||
参考 SalesManager 的 migrations/ 模式,所有迁移函数幂等可重复执行。
|
||||
"""
|
||||
|
||||
|
||||
def run_migrations():
|
||||
"""执行所有迁移(顺序执行,幂等)
|
||||
|
||||
延迟 import 避免 circular import(migrations 各子模块依赖 flask_app 的 db/_exec 等)。
|
||||
"""
|
||||
from migrations.tables import migrate_create_tables
|
||||
from migrations.columns import migrate_add_columns
|
||||
from migrations.data_fixes import migrate_fix_task_status, migrate_rename_tenant
|
||||
from migrations.seed import migrate_seed_users, migrate_seed_demo_data
|
||||
|
||||
migrate_create_tables()
|
||||
migrate_add_columns()
|
||||
migrate_fix_task_status()
|
||||
migrate_rename_tenant()
|
||||
migrate_seed_users()
|
||||
migrate_seed_demo_data()
|
||||
61
backend/migrations/columns.py
Normal file
61
backend/migrations/columns.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""migrations/columns.py — 加列迁移(老表补字段,幂等)"""
|
||||
|
||||
|
||||
def _add_column_if_missing(conn, table, column, ddl):
|
||||
"""检查列是否存在,不存在才加(幂等)"""
|
||||
from flask_app import _exec, mysql, logger
|
||||
|
||||
cur = conn.cursor(dictionary=True)
|
||||
cur.execute(f"SHOW COLUMNS FROM {table} LIKE %s", (column,))
|
||||
exists = cur.fetchone()
|
||||
cur.close()
|
||||
if not exists:
|
||||
try:
|
||||
_exec(conn, ddl)
|
||||
print(f"[migrate] {table}.{column} 列已添加")
|
||||
except mysql.connector.Error as e:
|
||||
logger.debug(f"add column {table}.{column} skipped: {e}")
|
||||
|
||||
|
||||
def migrate_add_columns():
|
||||
"""为老表补齐后续新增的字段"""
|
||||
from flask_app import db
|
||||
|
||||
conn = db()
|
||||
try:
|
||||
# tenant 字段(多工作台支持)
|
||||
for table in ["sales_leads", "follow_up_records", "business_proposals",
|
||||
"operation_projects", "product_versions", "finance_records",
|
||||
"project_tasks"]:
|
||||
_add_column_if_missing(conn, table, "tenant",
|
||||
f"ALTER TABLE {table} ADD COLUMN tenant VARCHAR(100) NOT NULL DEFAULT '科普·无界'")
|
||||
|
||||
# business_proposals 扩展字段
|
||||
_add_column_if_missing(conn, "business_proposals", "proposal_type",
|
||||
"ALTER TABLE business_proposals ADD COLUMN proposal_type VARCHAR(100) NOT NULL DEFAULT '业务方案'")
|
||||
_add_column_if_missing(conn, "business_proposals", "notes",
|
||||
"ALTER TABLE business_proposals ADD COLUMN notes VARCHAR(2000) NOT NULL DEFAULT ''")
|
||||
|
||||
# product_versions 扩展字段
|
||||
_add_column_if_missing(conn, "product_versions", "platform",
|
||||
"ALTER TABLE product_versions ADD COLUMN platform VARCHAR(100) NOT NULL DEFAULT ''")
|
||||
|
||||
# project_tasks 扩展字段
|
||||
_add_column_if_missing(conn, "project_tasks", "status",
|
||||
"ALTER TABLE project_tasks ADD COLUMN status VARCHAR(50) NOT NULL DEFAULT '未开始'")
|
||||
_add_column_if_missing(conn, "project_tasks", "sort_order",
|
||||
"ALTER TABLE project_tasks ADD COLUMN sort_order INT NOT NULL DEFAULT 0")
|
||||
_add_column_if_missing(conn, "project_tasks", "priority",
|
||||
"ALTER TABLE project_tasks ADD COLUMN priority VARCHAR(10) NOT NULL DEFAULT 'P2'")
|
||||
|
||||
# project_finances 12 个月度预算字段(确收/毛利/回款/费用)
|
||||
for m in ["01","02","03","04","05","06","07","08","09","10","11","12"]:
|
||||
for field in ["rev", "gross", "payment", "cost"]:
|
||||
col = f"{field}_2026_{m}"
|
||||
_add_column_if_missing(conn, "project_finances", col,
|
||||
f"ALTER TABLE project_finances ADD COLUMN {col} DOUBLE NOT NULL DEFAULT 0")
|
||||
|
||||
conn.commit()
|
||||
print("[migrate] 加列迁移完成")
|
||||
finally:
|
||||
conn.close()
|
||||
49
backend/migrations/data_fixes.py
Normal file
49
backend/migrations/data_fixes.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""migrations/data_fixes.py — 数据修正迁移(修复脏数据、变更枚举值)"""
|
||||
|
||||
|
||||
def migrate_fix_task_status():
|
||||
"""修正 project_tasks 中非法的 status 值"""
|
||||
from flask_app import db, _exec, mysql, logger
|
||||
|
||||
conn = db()
|
||||
try:
|
||||
fixes = [
|
||||
"UPDATE project_tasks SET status='未开始' WHERE status='' OR status IS NULL",
|
||||
"UPDATE project_tasks SET status='已结束' WHERE status='done'",
|
||||
"UPDATE project_tasks SET status='进行中' WHERE status='验收中'",
|
||||
]
|
||||
for sql in fixes:
|
||||
try:
|
||||
cur = _exec(conn, sql)
|
||||
affected = cur.rowcount
|
||||
cur.close()
|
||||
if affected:
|
||||
print(f"[migrate] 修正 {affected} 条任务状态")
|
||||
except mysql.connector.Error as e:
|
||||
logger.warning(f"task status fix skipped: {e}")
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def migrate_rename_tenant():
|
||||
"""工作台重命名:无界·无界 → 学会·无界"""
|
||||
from flask_app import db, _exec, mysql
|
||||
|
||||
conn = db()
|
||||
try:
|
||||
tables = ["user_tenants", "sales_leads", "follow_up_records", "business_proposals",
|
||||
"operation_projects", "product_versions", "finance_records", "project_tasks",
|
||||
"project_finances"]
|
||||
for table in tables:
|
||||
try:
|
||||
cur = _exec(conn, f"UPDATE {table} SET tenant='学会·无界' WHERE tenant='无界·无界'")
|
||||
affected = cur.rowcount
|
||||
cur.close()
|
||||
if affected:
|
||||
print(f"[migrate] {table}: {affected} 条记录 tenant 改为 '学会·无界'")
|
||||
except mysql.connector.Error:
|
||||
pass
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
55
backend/migrations/seed.py
Normal file
55
backend/migrations/seed.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""migrations/seed.py — 初始化默认用户和示例数据(仅在空库时执行)"""
|
||||
|
||||
from datetime import date
|
||||
|
||||
|
||||
def migrate_seed_users():
|
||||
"""初始化默认用户和工作台权限(仅空库时执行)"""
|
||||
from flask_app import db, _exec, one, generate_password_hash
|
||||
|
||||
conn = db()
|
||||
try:
|
||||
if one(conn, "SELECT id FROM users LIMIT 1"):
|
||||
return # 已有用户,跳过
|
||||
|
||||
default_users = [
|
||||
("qiukai", "yxcowork2026", "qiukai", "admin"),
|
||||
("kepu", "kepu123", "科普负责人", "opc_owner"),
|
||||
("keyan", "keyan123", "科研负责人", "opc_owner"),
|
||||
("yihuan", "yihuan123", "医患负责人", "opc_owner"),
|
||||
("mcn", "mcn123", "MCN负责人", "opc_owner"),
|
||||
("wuji", "wuji123", "无界负责人", "opc_owner"),
|
||||
]
|
||||
for username, pwd, display, role in default_users:
|
||||
_exec(conn, "INSERT INTO users (username, password_hash, display_name, role, created_at) VALUES (?,?,?,?,?)",
|
||||
(username, generate_password_hash(pwd, "pbkdf2:sha256"), display, role, date.today().isoformat()))
|
||||
|
||||
# 绑定工作台
|
||||
tenant_map = [
|
||||
("kepu", "科普·无界"), ("keyan", "科研·无界"), ("yihuan", "医患·无界"),
|
||||
("mcn", "MCN·无界"), ("wuji", "学会·无界"),
|
||||
]
|
||||
for uname, tenant in tenant_map:
|
||||
u = one(conn, "SELECT id FROM users WHERE username=?", (uname,))
|
||||
if u:
|
||||
_exec(conn, "INSERT INTO user_tenants (user_id, tenant) VALUES (?,?)", (u["id"], tenant))
|
||||
|
||||
conn.commit()
|
||||
print("[migrate] 默认用户已初始化")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def migrate_seed_demo_data():
|
||||
"""填充初始示例数据(仅在空库时执行)"""
|
||||
from flask_app import db, one, seed_db
|
||||
|
||||
conn = db()
|
||||
try:
|
||||
if one(conn, "SELECT id FROM sales_leads LIMIT 1"):
|
||||
return # 已有数据,跳过
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
seed_db()
|
||||
print("[migrate] 示例数据已填充")
|
||||
162
backend/migrations/tables.py
Normal file
162
backend/migrations/tables.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""migrations/tables.py — 建表迁移(所有表的 CREATE TABLE IF NOT EXISTS)"""
|
||||
|
||||
|
||||
def migrate_create_tables():
|
||||
"""确保所有业务表存在(幂等)"""
|
||||
from flask_app import db, _exec, mysql, logger
|
||||
|
||||
conn = db()
|
||||
try:
|
||||
tables = [
|
||||
"""CREATE TABLE IF NOT EXISTS sales_leads (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
target_customer VARCHAR(1000) NOT NULL,
|
||||
priority VARCHAR(1000) NOT NULL DEFAULT 'P1',
|
||||
status VARCHAR(1000) NOT NULL DEFAULT '待跟进',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS follow_up_records (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
target_type VARCHAR(1000) NOT NULL,
|
||||
target_id INT NOT NULL,
|
||||
followed_at VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
follower VARCHAR(1000) NOT NULL DEFAULT '慰心',
|
||||
follow_up_method VARCHAR(1000) NOT NULL DEFAULT '记录',
|
||||
content VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
next_action VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
next_follow_up_at VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS business_proposals (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_or_project_name VARCHAR(1000) NOT NULL,
|
||||
version VARCHAR(1000) NOT NULL,
|
||||
description VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
status VARCHAR(1000) NOT NULL DEFAULT '草稿',
|
||||
created_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS operation_projects (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
project_name VARCHAR(1000) NOT NULL,
|
||||
project_version VARCHAR(1000) NOT NULL DEFAULT 'v1.0',
|
||||
project_type VARCHAR(1000) NOT NULL DEFAULT 'opportunity',
|
||||
project_status VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
current_stage VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
owner VARCHAR(1000) NOT NULL DEFAULT '慰心',
|
||||
start_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
end_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
target_customer VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
customer_need VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
expected_contract_amount DOUBLE NOT NULL DEFAULT 0,
|
||||
expected_sign_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
sign_probability DOUBLE NOT NULL DEFAULT 0,
|
||||
next_action VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
related_business_proposal_id INTEGER,
|
||||
sop_file_id INTEGER,
|
||||
sop_stage VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
execution_progress DOUBLE NOT NULL DEFAULT 0,
|
||||
current_deliverable VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
risks VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
notes VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS product_versions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
product_name VARCHAR(1000) NOT NULL,
|
||||
version VARCHAR(1000) NOT NULL,
|
||||
version_goal VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
feature_list VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
launch_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
status VARCHAR(1000) NOT NULL DEFAULT '规划中',
|
||||
notes VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS finance_records (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
month VARCHAR(1000) NOT NULL,
|
||||
project_name VARCHAR(1000) NOT NULL DEFAULT '科普(慰心斋)',
|
||||
record_type VARCHAR(1000) NOT NULL,
|
||||
category VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
amount DOUBLE NOT NULL DEFAULT 0,
|
||||
occurred_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
notes VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS file_assets (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
module VARCHAR(1000) NOT NULL,
|
||||
owner_id INT NOT NULL,
|
||||
owner_version VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
file_category VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
file_name VARCHAR(1000) NOT NULL,
|
||||
file_type VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
file_size INTEGER NOT NULL DEFAULT 0,
|
||||
file_path VARCHAR(1000) NOT NULL,
|
||||
is_external INTEGER NOT NULL DEFAULT 0,
|
||||
notes VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS project_tasks (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
project_id INTEGER NOT NULL,
|
||||
phase VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
milestone VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
task VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
owner VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
due_date VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
blockers VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
notes VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(100) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(100) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL DEFAULT 'opc_owner',
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS user_tenants (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
tenant VARCHAR(100) NOT NULL,
|
||||
UNIQUE KEY (user_id, tenant)
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS project_finances (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant VARCHAR(100) NOT NULL DEFAULT '科普·无界',
|
||||
project_id VARCHAR(100) NOT NULL DEFAULT '',
|
||||
business_type VARCHAR(100) NOT NULL DEFAULT '',
|
||||
customer_name VARCHAR(200) NOT NULL DEFAULT '',
|
||||
sign_amount DOUBLE NOT NULL DEFAULT 0,
|
||||
sign_month VARCHAR(20) NOT NULL DEFAULT '',
|
||||
status VARCHAR(50) NOT NULL DEFAULT '待签约',
|
||||
sales_person VARCHAR(100) NOT NULL DEFAULT '',
|
||||
total_rev DOUBLE NOT NULL DEFAULT 0,
|
||||
total_gross DOUBLE NOT NULL DEFAULT 0,
|
||||
budget_data TEXT,
|
||||
created_at VARCHAR(30) NOT NULL DEFAULT '',
|
||||
updated_at VARCHAR(30) NOT NULL DEFAULT ''
|
||||
)""",
|
||||
]
|
||||
|
||||
for ddl in tables:
|
||||
try:
|
||||
_exec(conn, ddl)
|
||||
conn.commit()
|
||||
except mysql.connector.Error as e:
|
||||
logger.debug(f"create table skipped: {e}")
|
||||
conn.commit()
|
||||
|
||||
print("[migrate] 所有业务表已就绪")
|
||||
finally:
|
||||
conn.close()
|
||||
@@ -1,4 +1,4 @@
|
||||
// drawer.js — 详情抽屉 + 评论 + 转移 + 删除
|
||||
// drawer.js — 详情抽屉 + 评论 + 删除
|
||||
|
||||
function drawerField(icon, label, name, value, multiline = false, customControl = null) {
|
||||
const safeValue = esc(value || "");
|
||||
@@ -37,7 +37,7 @@ function openDrawer(resource, id) {
|
||||
const followupTarget = resource === "sales" ? "sales" : resource === "proposals" ? "proposal" : resource === "operations" ? "operation" : resource === "products" ? "product" : "";
|
||||
const title = esc(item.target_customer || item.project_name || (item.customer_or_project_name ? `${item.customer_or_project_name} · ${item.proposal_type || ''}` : "") || item.product_name);
|
||||
const titleForAttr = esc(item.target_customer || item.project_name || (item.customer_or_project_name ? `${item.customer_or_project_name} · ${item.proposal_type || ''}` : "") || item.product_name);
|
||||
drawer.innerHTML = `<div class="drawer-panel"><div class="sticky top-0 z-10 flex items-center justify-between border-b border-slate-200 bg-white/95 px-5 py-3 backdrop-blur"><div><p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-400">Detail Drawer</p><div class="flex items-center gap-2"><h2 class="drawer-title text-[17px] font-semibold leading-6 text-slate-900">${title}</h2><span id="drawerSaveStatus" class="save-status"></span></div></div><div class="flex items-center gap-2"><button class="btn btn-ghost btn-sm text-blue-600 hover:bg-blue-50" onclick="openTransferModal('${resource}', ${id}, '${titleForAttr}')" ${resource === 'operations' ? '' : 'style="display:none"'}><i data-lucide="move-right"></i>转移</button><button class="btn btn-ghost btn-sm text-red-600 hover:bg-red-50" onclick="deleteDrawerItem('${resource}', ${id})"><i data-lucide="trash-2"></i>删除</button><button class="btn btn-ghost btn-sm" onclick="closeDrawer()">关闭</button></div></div><div class="grid gap-5 p-5">
|
||||
drawer.innerHTML = `<div class="drawer-panel"><div class="sticky top-0 z-10 flex items-center justify-between border-b border-slate-200 bg-white/95 px-5 py-3 backdrop-blur"><div><p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-400">Detail Drawer</p><div class="flex items-center gap-2"><h2 class="drawer-title text-[17px] font-semibold leading-6 text-slate-900">${title}</h2><span id="drawerSaveStatus" class="save-status"></span></div></div><div class="flex items-center gap-2"><button class="btn btn-ghost btn-sm text-red-600 hover:bg-red-50" onclick="deleteDrawerItem('${resource}', ${id})"><i data-lucide="trash-2"></i>删除</button><button class="btn btn-ghost btn-sm" onclick="closeDrawer()">关闭</button></div></div><div class="grid gap-5 p-5">
|
||||
<section>
|
||||
<h3 class="drawer-section-title">属性</h3>
|
||||
<form id="drawerForm" class="drawer-fields">
|
||||
@@ -172,33 +172,6 @@ window.deleteDrawerItem = async (resource, id) => {
|
||||
}
|
||||
};
|
||||
|
||||
window.openTransferModal = (resource, id, title) => {
|
||||
document.querySelector("#transfer-resource").value = resource;
|
||||
document.querySelector("#transfer-id").value = id;
|
||||
document.querySelector("#transfer-title-text").textContent = "将「" + title + "」转移到:";
|
||||
document.querySelector("#transferModal").classList.remove("hidden");
|
||||
};
|
||||
|
||||
window.closeTransferModal = () => {
|
||||
document.querySelector("#transferModal").classList.add("hidden");
|
||||
};
|
||||
|
||||
window.submitTransfer = async (event) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const resource = form.querySelector('[name="transfer_resource"]').value;
|
||||
const id = form.querySelector('[name="transfer_id"]').value;
|
||||
const newTenant = form.querySelector('[name="transfer_tenant"]').value;
|
||||
try {
|
||||
await api(`/api/${resource}/${id}`, { method: "PUT", body: JSON.stringify({ data: { tenant: newTenant } }) });
|
||||
closeTransferModal();
|
||||
closeDrawer();
|
||||
await load();
|
||||
} catch (error) {
|
||||
toast("转移失败:" + error.message, "error");
|
||||
}
|
||||
};
|
||||
|
||||
// Squire 富文本编辑器
|
||||
window.squireInstances = {};
|
||||
window.squireCmd = (cmd) => {
|
||||
|
||||
@@ -91,30 +91,6 @@
|
||||
</div><!-- 关闭 flex 容器 -->
|
||||
<aside id="drawer" class="drawer" aria-hidden="true"></aside>
|
||||
<div id="taskModal" class="task-modal"></div>
|
||||
<div id="transferModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40" onclick="closeTransferModal()">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-sm mx-4" onclick="event.stopPropagation()">
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
||||
<h3 class="text-lg font-semibold text-slate-800">跨工作台转移</h3>
|
||||
<button class="btn btn-ghost btn-sm rounded-full w-8 h-8 p-0" onclick="closeTransferModal()"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<form onsubmit="submitTransfer(event)" class="p-6 grid gap-4">
|
||||
<input type="hidden" name="transfer_resource" id="transfer-resource" value="">
|
||||
<input type="hidden" name="transfer_id" id="transfer-id" value="">
|
||||
<p id="transfer-title-text" class="text-sm text-slate-600"></p>
|
||||
<label class="block"><span class="text-xs font-medium text-slate-500">目标工作台</span>
|
||||
<select name="transfer_tenant" class="form-ctrl mt-1">
|
||||
<option value="科普·无界">科普·无界</option>
|
||||
<option value="科研·无界">科研·无界</option>
|
||||
<option value="医患·无界">医患·无界</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex justify-end gap-3 pt-3">
|
||||
<button type="button" class="btn btn-ghost btn-sm" onclick="closeTransferModal()">取消</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm">确认转移</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 新增项目模态框 -->
|
||||
<div id="newProjectModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40" onclick="closeNewProjectModal()">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md mx-4" onclick="event.stopPropagation()">
|
||||
|
||||
Reference in New Issue
Block a user