ZHIWEI666 commited on
Commit
04b9634
·
verified ·
1 Parent(s): 263221b

提示词将分类添加改为云端管理

Browse files
router_prompts.py CHANGED
@@ -16,7 +16,7 @@ from fastapi import APIRouter, HTTPException, Depends, Request
16
  from sqlalchemy.orm import Session
17
  from models import PromptCreate, PromptUpdate, PromptCategoryAdd
18
  import 数据库连接 as db
19
- from 安全认证 import require_auth, optional_auth
20
  from db_utils import record_view, sort_cache
21
  from database_sql import get_db
22
  from models_sql import Wallet, Transaction, Ownership
@@ -171,8 +171,10 @@ async def get_prompt_categories(prompt_type: str = None):
171
  @limiter.limit("5/minute") # 🔒 限流:防止分类刷屏
172
  async def add_prompt_category(request: Request, body: PromptCategoryAdd, current_user: str = Depends(require_auth)):
173
  """
174
- 添加提示词分类(登录用户均可,重名去重)
175
  """
 
 
176
  if body.prompt_type not in VALID_PROMPT_TYPES:
177
  raise HTTPException(status_code=400, detail="分类所属类型必须是 image、video 或 music")
178
 
@@ -212,6 +214,48 @@ async def add_prompt_category(request: Request, body: PromptCategoryAdd, current
212
  return {"status": "success", "data": result_holder["data"]}
213
 
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  # ==========================================
216
  # 📝 提示词CRUD接口
217
  # ==========================================
 
16
  from sqlalchemy.orm import Session
17
  from models import PromptCreate, PromptUpdate, PromptCategoryAdd
18
  import 数据库连接 as db
19
+ from 安全认证 import require_auth, optional_auth, is_admin
20
  from db_utils import record_view, sort_cache
21
  from database_sql import get_db
22
  from models_sql import Wallet, Transaction, Ownership
 
171
  @limiter.limit("5/minute") # 🔒 限流:防止分类刷屏
172
  async def add_prompt_category(request: Request, body: PromptCategoryAdd, current_user: str = Depends(require_auth)):
173
  """
174
+ 添加提示词分类(仅管理员,入口在云端管理界面,重名去重)
175
  """
176
+ if not is_admin(current_user):
177
+ raise HTTPException(status_code=403, detail="需要管理员权限")
178
  if body.prompt_type not in VALID_PROMPT_TYPES:
179
  raise HTTPException(status_code=400, detail="分类所属类型必须是 image、video 或 music")
180
 
 
214
  return {"status": "success", "data": result_holder["data"]}
215
 
216
 
217
+ @router.delete("/api/prompt-categories")
218
+ @limiter.limit("5/minute") # 🔒 限流:与添加分类一致
219
+ async def delete_prompt_category(request: Request, prompt_type: str = None, name: str = None,
220
+ current_user: str = Depends(require_auth)):
221
+ """
222
+ 删除提示词分类(仅管理员)
223
+ - prompt_type: image / video / music
224
+ - name: 分类名称
225
+ ⚠️ 已使用该分类的提示词不受影响(分类字段保留原文本,仅从可选列表移除)
226
+ """
227
+ if not is_admin(current_user):
228
+ raise HTTPException(status_code=403, detail="需要管理员权限")
229
+ if prompt_type not in VALID_PROMPT_TYPES:
230
+ raise HTTPException(status_code=400, detail="分类所属类型必须是 image、video 或 music")
231
+
232
+ name = (name or "").strip()
233
+ if not name:
234
+ raise HTTPException(status_code=400, detail="分类名称不能为空")
235
+
236
+ result_holder = {}
237
+
238
+ def updater(data):
239
+ if not isinstance(data, dict):
240
+ result_holder["error"] = "not_found"
241
+ return
242
+ cat_list = data.get(prompt_type, [])
243
+ if name not in cat_list:
244
+ result_holder["error"] = "not_found"
245
+ return
246
+ cat_list.remove(name)
247
+ data[prompt_type] = cat_list
248
+ result_holder["data"] = cat_list
249
+
250
+ db.atomic_update("prompt_categories.json", updater, default_data={})
251
+
252
+ if result_holder.get("error") == "not_found":
253
+ raise HTTPException(status_code=404, detail="分类不存在")
254
+
255
+ logger.info(f"PROMPT_CATEGORY_DELETE | user={current_user} | type={prompt_type} | name={name}")
256
+ return {"status": "success", "data": result_holder["data"]}
257
+
258
+
259
  # ==========================================
260
  # 📝 提示词CRUD接口
261
  # ==========================================
管理界面/app.js CHANGED
@@ -33,6 +33,7 @@
33
  disputeStatus: '',
34
  bannerImageUrl: '',
35
  bannerDetailImageUrl: '',
 
36
  };
37
 
38
  // -------------------- 工具 --------------------
@@ -680,6 +681,7 @@
680
  disputes: '申诉仲裁',
681
  config: '版本管理',
682
  broadcast: '公告与广告',
 
683
  scripts: '脚本执行'
684
  };
685
 
@@ -707,6 +709,7 @@
707
  if (name === 'disputes') refreshDisputes();
708
  if (name === 'config') loadVersionConfig();
709
  if (name === 'broadcast') refreshBannerStatus();
 
710
  }
711
 
712
  function bindTabs() {
@@ -1042,6 +1045,100 @@
1042
  });
1043
  }
1044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1045
  // -------------------- Toast --------------------
1046
  function toast(message, type) {
1047
  const container = $('#toast-container');
@@ -1332,6 +1429,7 @@
1332
  bindDisputes();
1333
  bindVersion();
1334
  bindBroadcast();
 
1335
  bindScripts();
1336
 
1337
  if (state.token) {
 
33
  disputeStatus: '',
34
  bannerImageUrl: '',
35
  bannerDetailImageUrl: '',
36
+ pcatType: 'image',
37
  };
38
 
39
  // -------------------- 工具 --------------------
 
681
  disputes: '申诉仲裁',
682
  config: '版本管理',
683
  broadcast: '公告与广告',
684
+ promptcats: '提示词分类',
685
  scripts: '脚本执行'
686
  };
687
 
 
709
  if (name === 'disputes') refreshDisputes();
710
  if (name === 'config') loadVersionConfig();
711
  if (name === 'broadcast') refreshBannerStatus();
712
+ if (name === 'promptcats') refreshPromptCats();
713
  }
714
 
715
  function bindTabs() {
 
1045
  });
1046
  }
1047
 
1048
+ // -------------------- 🗂️ 提示词分类 --------------------
1049
+ const PCAT_TYPE_LABELS = { image: '图像提示词', video: '视频提示词', music: '音乐提示词' };
1050
+
1051
+ function setPcatState(stateName, message) {
1052
+ $('#pcat-loading').hidden = stateName !== 'loading';
1053
+ $('#pcat-empty').hidden = stateName !== 'empty';
1054
+ $('#pcat-error').hidden = stateName !== 'error';
1055
+ $('#pcat-list').hidden = stateName !== 'data';
1056
+ if (stateName === 'error' && message) $('#pcat-error-msg').textContent = message;
1057
+ }
1058
+
1059
+ async function refreshPromptCats() {
1060
+ try {
1061
+ setPcatState('loading');
1062
+ const res = await apiRequest('GET', '/api/prompt-categories?prompt_type=' + state.pcatType);
1063
+ const list = Array.isArray(res && res.data) ? res.data : [];
1064
+ renderPromptCats(list);
1065
+ } catch (err) {
1066
+ if (err instanceof ApiError && err.status === 401) return; // 已自动跳回登录
1067
+ setPcatState('error', err.message || '加载失败');
1068
+ }
1069
+ }
1070
+
1071
+ function renderPromptCats(list) {
1072
+ const badge = $('#pcat-count-badge');
1073
+ if (badge) badge.textContent = `共 ${list.length} 个`;
1074
+ if (!list.length) { setPcatState('empty'); return; }
1075
+ setPcatState('data');
1076
+ const wrap = $('#pcat-list');
1077
+ wrap.innerHTML = '';
1078
+ const frag = document.createDocumentFragment();
1079
+ list.forEach((cat) => {
1080
+ const row = document.createElement('div');
1081
+ row.className = 'pcat-row';
1082
+ const name = document.createElement('span');
1083
+ name.className = 'pcat-name';
1084
+ name.textContent = cat;
1085
+ const del = document.createElement('button');
1086
+ del.type = 'button';
1087
+ del.className = 'btn btn-ghost btn-sm btn-danger-text';
1088
+ del.textContent = '删除';
1089
+ del.addEventListener('click', () => confirmDeletePromptCat(cat));
1090
+ row.appendChild(name);
1091
+ row.appendChild(del);
1092
+ frag.appendChild(row);
1093
+ });
1094
+ wrap.appendChild(frag);
1095
+ }
1096
+
1097
+ function confirmDeletePromptCat(cat) {
1098
+ confirmAction({
1099
+ title: '删除分类',
1100
+ message: `确定删除${PCAT_TYPE_LABELS[state.pcatType] || ''}分类「${cat}」?\n已使用该分类的提示词不受影响。`,
1101
+ okText: '确认删除',
1102
+ danger: true,
1103
+ onConfirm: async () => {
1104
+ await apiRequest('DELETE', '/api/prompt-categories?prompt_type=' + state.pcatType + '&name=' + encodeURIComponent(cat));
1105
+ toast('分类已删除', 'success');
1106
+ await refreshPromptCats();
1107
+ }
1108
+ });
1109
+ }
1110
+
1111
+ function bindPromptCats() {
1112
+ $('#btn-pcat-refresh').addEventListener('click', () => refreshPromptCats());
1113
+ $$('#pcat-type-tabs .filter-tab').forEach((tab) => {
1114
+ tab.addEventListener('click', () => {
1115
+ state.pcatType = tab.dataset.type;
1116
+ $$('#pcat-type-tabs .filter-tab').forEach((t) => t.classList.toggle('is-active', t === tab));
1117
+ refreshPromptCats();
1118
+ });
1119
+ });
1120
+ const addBtn = $('#btn-pcat-add');
1121
+ const nameInput = $('#pcat-name');
1122
+ const submitAdd = async () => {
1123
+ const name = nameInput.value.trim();
1124
+ if (!name) { toast('请输入分类名称', 'warning'); nameInput.focus(); return; }
1125
+ if (name.length > 8) { toast('分类名称不能超过 8 个字', 'warning'); return; }
1126
+ setBusy(addBtn, true);
1127
+ try {
1128
+ await apiRequest('POST', '/api/prompt-categories', { prompt_type: state.pcatType, name });
1129
+ nameInput.value = '';
1130
+ toast('分类已添加', 'success');
1131
+ await refreshPromptCats();
1132
+ } catch (err) {
1133
+ toast(err.message || '添加分类失败', 'error');
1134
+ } finally {
1135
+ setBusy(addBtn, false);
1136
+ }
1137
+ };
1138
+ addBtn.addEventListener('click', submitAdd);
1139
+ nameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') submitAdd(); });
1140
+ }
1141
+
1142
  // -------------------- Toast --------------------
1143
  function toast(message, type) {
1144
  const container = $('#toast-container');
 
1429
  bindDisputes();
1430
  bindVersion();
1431
  bindBroadcast();
1432
+ bindPromptCats();
1433
  bindScripts();
1434
 
1435
  if (state.token) {
管理界面/index.html CHANGED
@@ -97,13 +97,14 @@
97
  </header>
98
 
99
  <main class="main-shell">
100
- <!-- 🧭 功能页签导航:插件 / 提现 / 仲裁 / 版本 / 公告广告 / 脚本 -->
101
  <nav class="tab-bar" role="tablist" aria-label="功能导航">
102
  <button type="button" class="tab-item is-active" role="tab" aria-selected="true" data-tab="plugins">🔌 第三方插件</button>
103
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="withdrawals">💰 提现管理</button>
104
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="disputes">⚖️ 申诉仲裁</button>
105
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="config">🏷️ 版本管理</button>
106
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="broadcast">📢 公告与广告</button>
 
107
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="scripts">🖥️ 脚本执行</button>
108
  </nav>
109
 
@@ -371,7 +372,71 @@
371
  </div>
372
  </div>
373
 
374
- <!-- ============ 页签 6:脚本执行 ============ -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  <div class="tab-panel" id="panel-scripts" role="tabpanel" hidden>
376
  <section class="page-head">
377
  <div class="page-head-text">
 
97
  </header>
98
 
99
  <main class="main-shell">
100
+ <!-- 🧭 功能页签导航:插件 / 提现 / 仲裁 / 版本 / 公告广告 / 提示词分类 / 脚本 -->
101
  <nav class="tab-bar" role="tablist" aria-label="功能导航">
102
  <button type="button" class="tab-item is-active" role="tab" aria-selected="true" data-tab="plugins">🔌 第三方插件</button>
103
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="withdrawals">💰 提现管理</button>
104
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="disputes">⚖️ 申诉仲裁</button>
105
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="config">🏷️ 版本管理</button>
106
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="broadcast">📢 公告与广告</button>
107
+ <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="promptcats">🗂️ 提示词分类</button>
108
  <button type="button" class="tab-item" role="tab" aria-selected="false" data-tab="scripts">🖥️ 脚本执行</button>
109
  </nav>
110
 
 
372
  </div>
373
  </div>
374
 
375
+ <!-- ============ 页签 6:提示词分类 ============ -->
376
+ <div class="tab-panel" id="panel-promptcats" role="tabpanel" hidden>
377
+ <section class="page-head">
378
+ <div class="page-head-text">
379
+ <h1>提示词分类</h1>
380
+ <p class="page-sub">管理提示词市场图像/视频/音乐三个子界面的可选分类。</p>
381
+ </div>
382
+ <div class="page-head-actions">
383
+ <button type="button" id="btn-pcat-refresh" class="btn btn-secondary" title="刷新">
384
+ <span class="btn-label">刷新</span>
385
+ </button>
386
+ </div>
387
+ </section>
388
+
389
+ <div class="filter-tabs" id="pcat-type-tabs">
390
+ <button type="button" class="filter-tab is-active" data-type="image">🎨 图像提示词</button>
391
+ <button type="button" class="filter-tab" data-type="video">🎬 视频提示词</button>
392
+ <button type="button" class="filter-tab" data-type="music">🎵 音乐提示词</button>
393
+ </div>
394
+
395
+ <div class="admin-card">
396
+ <div class="admin-card-head">
397
+ <h3>➕ 添加分类</h3>
398
+ <span class="badge badge-warning">管理员特权</span>
399
+ </div>
400
+ <div class="admin-card-body">
401
+ <div class="script-row">
402
+ <label class="field script-field">
403
+ <span class="field-label">分类名称</span>
404
+ <input type="text" id="pcat-name" maxlength="8" placeholder="输入分类名称(8 字以内)…">
405
+ </label>
406
+ <button type="button" id="btn-pcat-add" class="btn btn-primary btn-script-run">
407
+ <span class="btn-label">添加</span>
408
+ <span class="btn-spinner" aria-hidden="true"></span>
409
+ </button>
410
+ </div>
411
+ </div>
412
+ </div>
413
+
414
+ <div class="admin-card">
415
+ <div class="admin-card-head">
416
+ <h3>📋 当前分类</h3>
417
+ <span class="badge" id="pcat-count-badge">—</span>
418
+ </div>
419
+ <div class="admin-card-body">
420
+ <div id="pcat-loading" class="state-block" hidden>
421
+ <div class="spinner-lg" aria-hidden="true"></div>
422
+ <p>正在加载分类列表…</p>
423
+ </div>
424
+ <div id="pcat-empty" class="state-block" hidden>
425
+ <div class="empty-mark">∅</div>
426
+ <h3>暂无分类</h3>
427
+ <p>在上方输入框中添加第一个分类。</p>
428
+ </div>
429
+ <div id="pcat-error" class="state-block state-error" hidden>
430
+ <div class="empty-mark">!</div>
431
+ <h3>加载失败</h3>
432
+ <p id="pcat-error-msg">请稍后重试。</p>
433
+ </div>
434
+ <div id="pcat-list"></div>
435
+ </div>
436
+ </div>
437
+ </div>
438
+
439
+ <!-- ============ 页签 7:脚本执行 ============ -->
440
  <div class="tab-panel" id="panel-scripts" role="tabpanel" hidden>
441
  <section class="page-head">
442
  <div class="page-head-text">
管理界面/style.css CHANGED
@@ -1209,6 +1209,20 @@ code, pre { font-family: var(--font-mono); }
1209
  .script-row { display: flex; gap: 10px; align-items: flex-end; }
1210
  .script-field { flex: 1; }
1211
  .btn-script-run { flex: none; min-height: 41px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1212
  .script-result {
1213
  margin: 0;
1214
  padding: 14px;
 
1209
  .script-row { display: flex; gap: 10px; align-items: flex-end; }
1210
  .script-field { flex: 1; }
1211
  .btn-script-run { flex: none; min-height: 41px; }
1212
+
1213
+ /* ---------- 🗂️ 提示词分类 ---------- */
1214
+ .pcat-row {
1215
+ display: flex;
1216
+ align-items: center;
1217
+ justify-content: space-between;
1218
+ gap: 12px;
1219
+ padding: 10px 14px;
1220
+ border: 1px solid var(--border);
1221
+ border-radius: var(--radius);
1222
+ background: var(--bg-tint);
1223
+ }
1224
+ .pcat-row + .pcat-row { margin-top: 8px; }
1225
+ .pcat-name { font-size: 14px; color: var(--text); }
1226
  .script-result {
1227
  margin: 0;
1228
  padding: 14px;