| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- <header class="resource-header">
- <div><p>DATA RESOURCE</p><h1>{{ schema()?.label || className() }}</h1><span>{{ className() }} · {{ page()?.total || 0 | number }} 条记录</span></div>
- <div class="header-actions"><button type="button" (click)="load(1, true)"><lucide-icon [img]="icons.RefreshCw" [size]="17" />刷新</button>@if (schema()?.writable && schema()?.creatable !== false) { <button class="primary" type="button" (click)="openCreate()"><lucide-icon [img]="icons.Plus" [size]="18" />新建记录</button> }</div>
- </header>
- <section class="resource-card">
- <div class="toolbar"><label><lucide-icon [img]="icons.Search" [size]="18" /><input [ngModel]="search()" (ngModelChange)="search.set($event)" (keyup.enter)="load(1)" placeholder="按名称、标题、账号或来源键搜索" /></label><button type="button" (click)="load(1)">查询</button></div>
- @if (className() === 'Grade') {
- <div class="bulk-toolbar"><strong>多级字典范围</strong><select [ngModel]="filterGradeCategoryId()" (ngModelChange)="filterGradeCategoryId.set(+$event); filterGradeParentId.set(0); load(1)"><option [ngValue]="0">请选择分类</option>@for (category of gradeCategories(); track category['objectId']) { <option [ngValue]="gradeCategoryIdOf(category)">{{ category['cateName'] }}({{ category['cateId'] }})</option> }</select><input class="bulk-node" type="number" min="0" [ngModel]="filterGradeParentId()" (ngModelChange)="filterGradeParentId.set(+$event)" placeholder="父选项 ID" /><button type="button" [disabled]="!filterGradeCategoryId()" (click)="load(1)">查看该层</button></div>
- }
- @if (className() === 'Search') {
- <div class="bulk-toolbar"><strong>快捷入口范围</strong><select [ngModel]="filterSearchNavigationType()" (ngModelChange)="filterSearchNavigationType.set(+$event); load(1)"><option [ngValue]="1">管理后台</option><option [ngValue]="2">用户中心</option></select><select [ngModel]="filterSearchNavigationState()" (ngModelChange)="filterSearchNavigationState.set(+$event); load(1)"><option [ngValue]="-100">全部状态</option><option [ngValue]="1">已启用</option><option [ngValue]="2">已停用</option></select><select [ngModel]="filterSearchNavigationElite()" (ngModelChange)="filterSearchNavigationElite.set(+$event); load(1)"><option [ngValue]="-100">全部推荐</option><option [ngValue]="1">已推荐</option><option [ngValue]="0">未推荐</option></select></div>
- }
- @if (className() === 'AdZone') {
- <div class="bulk-toolbar"><strong>广告位范围</strong><input [ngModel]="filterAdZoneType()" (ngModelChange)="filterAdZoneType.set($event)" (keyup.enter)="load(1)" placeholder="广告位类型(精确匹配)" /><select [ngModel]="filterAdStatus()" (ngModelChange)="filterAdStatus.set(+$event); load(1)"><option [ngValue]="-100">全部状态</option><option [ngValue]="99">已启用</option><option [ngValue]="0">已停用</option></select><button type="button" (click)="load(1)">筛选</button></div>
- }
- @if (className() === 'AdInfo') {
- <div class="bulk-toolbar"><strong>广告内容范围</strong><select [ngModel]="filterAdInfoZoneId()" (ngModelChange)="filterAdInfoZoneId.set($event); load(1)"><option value="">全部广告位</option>@for (zone of adZones(); track zone['objectId']) { <option [value]="adZoneIdOf(zone)">{{ adZoneLabel(zone) }}</option> }</select><select [ngModel]="filterAdStatus()" (ngModelChange)="filterAdStatus.set(+$event); load(1)"><option [ngValue]="-100">全部状态</option><option [ngValue]="99">已审核</option><option [ngValue]="0">未审核</option></select></div>
- }
- @if (className() === 'FontPicShape') {
- <div class="bulk-toolbar"><strong>图形素材范围</strong><select [ngModel]="filterFontShapeTypeId()" (ngModelChange)="filterFontShapeTypeId.set(+$event); load(1)"><option [ngValue]="0">全部分类</option>@for (type of fontShapeTypes(); track type['objectId']) { <option [ngValue]="fontShapeTypeIdOf(type)">{{ fontShapeTypeLabel(type) }}</option> }</select></div>
- }
- @if (bulkEnabled() && selectedIds().length) {
- <div class="bulk-toolbar"><strong>已选 {{ selectedIds().length }} 条</strong>
- @if (className() === '_User') {
- <input class="bulk-reason" maxlength="200" [ngModel]="userActionReason()" (ngModelChange)="userActionReason.set($event)" placeholder="状态原因(选填)" />
- <button class="danger-action" type="button" [disabled]="updatingUser()" (click)="applyBulkUserAction('lock')">停用</button><button type="button" [disabled]="updatingUser()" (click)="applyBulkUserAction('unlock')">解锁</button>
- <select [ngModel]="selectedUserGroupId()" (ngModelChange)="selectedUserGroupId.set($event)"><option [ngValue]="0">选择用户组</option>@for (group of userGroups(); track group['objectId']) { <option [ngValue]="groupIdOf(group)">{{ groupLabel(group) }}</option> }</select><button type="button" [disabled]="updatingUser() || !selectedUserGroupId()" (click)="applyBulkUserAction('move')">移动分组</button>
- } @else if (className() === 'CommonModel') {
- <select [ngModel]="selectedContentStatus()" (ngModelChange)="selectedContentStatus.set($event)"><option [ngValue]="99">已审核</option><option [ngValue]="0">待审核</option><option [ngValue]="-1">未通过</option><option [ngValue]="-3">草稿</option></select><button type="button" [disabled]="updatingContent()" (click)="applyBulkContentAction('status')">更新状态</button><button class="danger-action" type="button" [disabled]="updatingContent()" (click)="applyBulkContentAction('recycle')">回收</button><button type="button" [disabled]="updatingContent()" (click)="applyBulkContentAction('recover')">恢复</button>
- <input class="bulk-node" type="number" min="1" [ngModel]="targetContentNodeId()" (ngModelChange)="targetContentNodeId.set(+$event)" placeholder="节点 ID" /><button type="button" [disabled]="updatingContent() || !targetContentNodeId()" (click)="applyBulkContentAction('move')">移动节点</button>
- } @else if (className() === 'Node') {
- <button class="danger-action" type="button" [disabled]="updatingNode()" (click)="applyBulkNodeAction('recycle')">回收</button><button type="button" [disabled]="updatingNode()" (click)="applyBulkNodeAction('recover')">恢复</button>
- <input class="bulk-node" type="number" min="0" [ngModel]="targetNodeParentId()" (ngModelChange)="targetNodeParentId.set(+$event)" placeholder="父节点 ID" /><button type="button" [disabled]="updatingNode()" (click)="applyBulkNodeAction('move')">移动栏目</button>
- } @else if (className() === 'Special') {
- <input class="bulk-node" type="number" min="0" [ngModel]="targetSpecialParentId()" (ngModelChange)="targetSpecialParentId.set(+$event)" placeholder="父专题 ID" /><button type="button" [disabled]="updatingSpecial()" (click)="applyBulkSpecialMove()">移动专题</button>
- } @else if (className() === 'Guestbook') {
- <button type="button" [disabled]="updatingGuestbook()" (click)="applyBulkGuestbookAction('audit')">审核</button><button type="button" [disabled]="updatingGuestbook()" (click)="applyBulkGuestbookAction('unaudit')">取消审核</button><button class="danger-action" type="button" [disabled]="updatingGuestbook()" (click)="applyBulkGuestbookAction('recycle')">回收</button><button type="button" [disabled]="updatingGuestbook()" (click)="applyBulkGuestbookAction('recover')">恢复</button>
- } @else if (className() === 'Datadiccategory' || className() === 'Datadic') {
- <button type="button" [disabled]="updatingDictionary()" (click)="applyBulkDictionaryAction('enable')">启用</button><button type="button" [disabled]="updatingDictionary()" (click)="applyBulkDictionaryAction('disable')">停用</button><button class="danger-action" type="button" [disabled]="updatingDictionary()" (click)="applyBulkDictionaryAction('delete')">删除</button>
- } @else if (className() === 'GradeCate' || className() === 'Grade') {
- <button class="danger-action" type="button" [disabled]="updatingGrade()" (click)="applyBulkGradeDelete()">删除</button>
- } @else if (className() === 'Currency') {
- <button class="danger-action" type="button" [disabled]="updatingCurrency()" (click)="applyBulkCurrencyDelete()">删除</button>
- } @else if (className() === 'SysHoliday') {
- <button class="danger-action" type="button" [disabled]="updatingHoliday()" (click)="applyBulkHolidayDelete()">删除</button>
- } @else if (className() === 'Search') {
- <button type="button" [disabled]="updatingSearchNavigation()" (click)="applyBulkSearchNavigationAction('enable')">启用</button><button type="button" [disabled]="updatingSearchNavigation()" (click)="applyBulkSearchNavigationAction('disable')">停用</button><button type="button" [disabled]="updatingSearchNavigation()" (click)="applyBulkSearchNavigationAction('elite')">推荐</button><button type="button" [disabled]="updatingSearchNavigation()" (click)="applyBulkSearchNavigationAction('unelite')">取消推荐</button><input class="bulk-node" type="number" min="1" max="1000000" [ngModel]="searchNavigationOrderStart()" (ngModelChange)="searchNavigationOrderStart.set(+$event)" placeholder="起始排序" /><button type="button" [disabled]="updatingSearchNavigation()" (click)="applyBulkSearchNavigationAction('order')">按选择顺序排列</button><button class="danger-action" type="button" [disabled]="updatingSearchNavigation()" (click)="applyBulkSearchNavigationAction('delete')">删除</button>
- } @else if (className() === 'AdZone') {
- <button type="button" [disabled]="updatingAdvertisement()" (click)="applyBulkAdvertisementAction('active')">启用</button><button type="button" [disabled]="updatingAdvertisement()" (click)="applyBulkAdvertisementAction('pause')">停用</button><button class="danger-action" type="button" [disabled]="updatingAdvertisement()" (click)="applyBulkAdvertisementAction('delete')">删除</button>
- } @else if (className() === 'AdInfo') {
- <button type="button" [disabled]="updatingAdvertisement()" (click)="applyBulkAdvertisementAction('audit')">审核</button><button type="button" [disabled]="updatingAdvertisement()" (click)="applyBulkAdvertisementAction('unaudit')">取消审核</button><button class="danger-action" type="button" [disabled]="updatingAdvertisement()" (click)="applyBulkAdvertisementAction('delete')">删除</button>
- } @else if (className() === 'FontPicShape' || className() === 'FontPicShapeType') {
- <button class="danger-action" type="button" [disabled]="updatingFontShape()" (click)="applyBulkFontShapeDelete()">删除</button>
- } @else {
- <button type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('audit')">审核</button><button class="danger-action" type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('recycle')">回收</button><button type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('recover')">恢复</button><button type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('elite')">加精</button><button type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('top-section')">版块置顶</button><button type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('top-global')">全局置顶</button><button type="button" [disabled]="updatingBar()" (click)="applyBulkBarAction('top-undo')">取消排序</button>
- <input class="bulk-node" type="number" min="1" [ngModel]="targetBarCategoryId()" (ngModelChange)="targetBarCategoryId.set(+$event)" placeholder="版块 ID" /><button type="button" [disabled]="updatingBar() || !targetBarCategoryId()" (click)="applyBulkBarAction('move')">移动版块</button>
- }
- </div>
- }
- @if (bulkError()) { <div class="bulk-notice error">{{ bulkError() }}</div> }
- @if (bulkMessage()) { <div class="bulk-notice success">{{ bulkMessage() }}</div> }
- @if (error()) { <div class="inline-error">{{ error() }}</div> }
- @if (loading() && !page()) {
- <div class="empty"><lucide-icon class="spin" [img]="icons.LoaderCircle" [size]="26" />正在加载数据…</div>
- } @else {
- <div class="table-scroll"><table><thead><tr>@if (bulkEnabled()) { <th class="select-col"><input type="checkbox" aria-label="全选当前页" [checked]="allPageSelected()" (change)="togglePageSelection($any($event.target).checked)" /></th> }@for (field of columns(); track field.name) { <th>{{ field.name }}</th> }<th class="actions">操作</th></tr></thead><tbody>
- @for (row of page()?.results || []; track row['objectId']) {
- <tr>@if (bulkEnabled()) { <td class="select-col"><input type="checkbox" [attr.aria-label]="'选择 ' + row['objectId']" [checked]="isSelected(row['objectId'])" (change)="toggleSelection('' + row['objectId'], $any($event.target).checked)" /></td> }@for (field of columns(); track field.name) { <td [title]="display(row[field.name])">{{ display(row[field.name]) }}</td> }<td class="actions"><button type="button" [attr.aria-label]="schema()?.writable ? '编辑' : '查看'" (click)="openEdit(row)"><lucide-icon [img]="schema()?.writable ? icons.Pencil : icons.Eye" [size]="16" /></button>@if (schema()?.writable && !['Model', 'ModelField'].includes(className())) { <button class="danger" type="button" [attr.aria-label]="['CommonModel', 'Node', 'Guestbook'].includes(className()) ? '移入回收站' : '删除'" (click)="remove(row)"><lucide-icon [img]="icons.Trash2" [size]="16" /></button> }</td></tr>
- } @empty { <tr><td class="empty-cell" [attr.colspan]="columns().length + (bulkEnabled() ? 2 : 1)">没有符合条件的数据</td></tr> }
- </tbody></table></div>
- <footer class="pagination"><span>第 {{ page()?.page || 1 }} 页,共 {{ pageCount() }} 页</span><div><button type="button" [disabled]="(page()?.page || 1) <= 1 || loading()" (click)="load((page()?.page || 1) - 1)"><lucide-icon [img]="icons.ChevronLeft" [size]="17" />上一页</button><button type="button" [disabled]="(page()?.page || 1) * (page()?.pageSize || 20) >= (page()?.total || 0) || loading()" (click)="load((page()?.page || 1) + 1)">下一页<lucide-icon [img]="icons.ChevronRight" [size]="17" /></button></div></footer>
- }
- </section>
- @if (editorOpen()) {
- <button class="drawer-mask" type="button" aria-label="关闭编辑器" (click)="closeEditor()"></button>
- <aside class="editor-drawer">
- <header><div><span>{{ !schema()?.writable ? '查看记录' : draftId() ? '编辑记录' : '新建记录' }}</span><strong>{{ schema()?.label }}</strong></div><button type="button" (click)="closeEditor()"><lucide-icon [img]="icons.X" [size]="20" /></button></header>
- <div class="editor-fields">
- @if (className() === '_User' && !draftId()) {
- <section class="account-create">
- <div class="account-create-note"><strong>开通普通用户</strong><span>自动归入当前帐套与默认组 1,并初始化旧系统用户 ID、余额和经验字段。此入口不能授予管理员权限。</span></div>
- <label><span>登录账号 <small>3–64 位 · 必填</small></span><input autocomplete="off" [ngModel]="createUsername()" (ngModelChange)="createUsername.set($event)" /></label>
- <label><span>显示名 <small>选填</small></span><input autocomplete="off" [ngModel]="createDisplayName()" (ngModelChange)="createDisplayName.set($event)" /></label>
- <label><span>登录密码 <small>8–72 位 · 必填</small></span><input type="password" autocomplete="new-password" [ngModel]="createPassword()" (ngModelChange)="createPassword.set($event)" /></label>
- <label><span>确认密码 <small>必填</small></span><input type="password" autocomplete="new-password" [ngModel]="createConfirmPassword()" (ngModelChange)="createConfirmPassword.set($event)" /></label>
- <label><span>手机号 <small>选填</small></span><input inputmode="tel" autocomplete="off" [ngModel]="createMobile()" (ngModelChange)="createMobile.set($event)" /></label>
- <label><span>邀请账号 <small>选填 · 同帐套</small></span><input autocomplete="off" [ngModel]="createInviteUsername()" (ngModelChange)="createInviteUsername.set($event)" /></label>
- @if (createUserError()) { <div class="account-create-error">{{ createUserError() }}</div> }
- </section>
- } @else {
- @if (className() === 'Model' || className() === 'ModelField') {
- <section class="account-create-note"><strong>仅编辑安全元数据</strong><span>模型编号、表名、字段名、字段类型和排序属于数据库结构,已锁定。新增、删除、复制或改变字段类型需要 PostgreSQL DDL 与旧模板运行时,当前会明确返回迁移阻塞。</span></section>
- }
- @if (className() === 'Search') {
- <section class="account-create-note"><strong>现有快捷入口编辑</strong><span>旧数字 ID 在目标兼容层不可写,因此仅允许编辑现有入口;启停、推荐和排序请在列表批量工具中操作。旧全局后台起始页依赖的 SiteConfig 持久层已不存在。</span></section>
- }
- @if (className() === 'AdZone' || className() === 'AdInfo') {
- <section class="account-create-note"><strong>现有{{ className() === 'AdZone' ? '广告位' : '广告内容' }}编辑</strong><span>旧数字 ID 与 Parse 保留字段冲突,创建探测不会生成旧编号,因此当前只允许安全编辑现有记录;新增请求会返回明确迁移阻塞。</span></section>
- }
- @if (className() === 'FontPicShape' || className() === 'FontPicShapeType') {
- <section class="account-create-note"><strong>现有{{ className() === 'FontPicShape' ? '图形素材' : '图形分类' }}编辑</strong><span>目标兼容层无法生成旧数字 ID,因此仅维护现有记录;新增请求返回明确迁移阻塞。成品字体图和草稿没有目标 Schema,已单独列入数据阻塞。</span></section>
- }
- @for (field of schema()?.fields || []; track field.name) { @if (field.writable) {
- <label><span>{{ field.name }} <small>{{ field.type }}{{ field.required ? ' · 必填' : '' }}</small></span>
- @if (field.type === 'Boolean') { <input type="checkbox" [ngModel]="draft()[field.name] === true" (ngModelChange)="setField(field, $event)" /> }
- @else if (field.type === 'Object' || field.type === 'Array') { <textarea rows="5" [class.invalid]="fieldErrors()[field.name]" [ngModel]="inputValue(field)" (ngModelChange)="setField(field, $event)"></textarea>@if (fieldErrors()[field.name]; as fieldError) { <small class="field-error">{{ fieldError }}</small> } }
- @else { <input [type]="field.type === 'Number' ? 'number' : field.type === 'Date' ? 'datetime-local' : 'text'" [ngModel]="inputValue(field)" (ngModelChange)="setField(field, $event)" [placeholder]="field.type === 'Pointer' ? (field.targetClass + ' objectId') : field.name" /> }
- </label>
- } }
- @if (className() === 'AdInfo') {
- <section class="content-controls ad-info-controls">
- <header><div><strong>广告归属</strong><span>只能选择当前帐套真实广告位;名称、素材、链接、时间、权重与审核状态由专用云函数校验</span></div>@if (draftId()) { <b>ID {{ draft()['id'] }}</b> }</header>
- <label><span>所属广告位</span><select [ngModel]="targetAdZoneId()" (ngModelChange)="targetAdZoneId.set($event)"><option value="">请选择广告位</option>@for (zone of adZones(); track zone['objectId']) { <option [value]="adZoneIdOf(zone)">{{ adZoneLabel(zone) }}</option> }</select>@if (!adZones().length) { <small>当前帐套没有可用广告位</small> }</label>
- </section>
- }
- @if (className() === 'AdZone' && draftId()) {
- <section class="content-controls ad-preview-controls">
- <header><div><strong>当前生效广告预览</strong><span>仅展示已启用且处于开始/到期时间范围内的关联广告;HTML 以源码文本显示,避免在管理端执行旧脚本</span></div><b>ID {{ draft()['id'] }}</b></header>
- <button type="button" [disabled]="adPreviewLoading()" (click)="loadAdPreview()">{{ adPreviewLoading() ? '加载中…' : '刷新预览' }}</button>
- @if (adPreviewError()) { <div class="content-action-status error">{{ adPreviewError() }}</div> }
- <div class="ad-preview-list">@for (ad of adPreview(); track ad['objectId']) { <article><strong>{{ ad['name'] }}</strong><span>权重 {{ ad['priority'] || 0 }} · {{ display(ad['sdate']) }} 至 {{ display(ad['edate']) }}</span>@if (ad['image'] || ad['images']) { <code>{{ ad['image'] || ad['images'] }}</code> }@if (ad['text']) { <p>{{ ad['text'] }}</p> }@if (ad['html']) { <pre>{{ ad['html'] }}</pre> }</article> } @empty { @if (!adPreviewLoading() && !adPreviewError()) { <div class="content-action-status">当前没有生效广告</div> } }</div>
- </section>
- }
- @if (className() === 'FontPicShape') {
- <section class="content-controls">
- <header><div><strong>图形素材与分类</strong><span>可维护名称、备注和分类;原图路径只读,图片替换等待服务端文件存储适配器修复</span></div>@if (draftId()) { <b>ID {{ draft()['id'] }}</b> }</header>
- <label><span>所属图形分类</span><select [ngModel]="targetFontShapeTypeId()" (ngModelChange)="targetFontShapeTypeId.set(+$event)"><option [ngValue]="0">请选择分类</option>@for (type of fontShapeTypes(); track type['objectId']) { <option [ngValue]="fontShapeTypeIdOf(type)">{{ fontShapeTypeLabel(type) }}</option> }</select></label>
- @if (fontShapePreview()) { <div><img [src]="fontShapePreview()" alt="图形素材预览" width="120" height="120" /><code>{{ fontShapePreview() }}</code></div> }
- <div class="content-action-status error">图片替换暂不可用:当前 Parse Server 的 SDK 与 /files REST 接口都错误要求 MongoDB 连接串;本项目数据库为 PostgreSQL。云函数会对此返回明确 501,不会伪装上传成功。</div>
- </section>
- }
- @if (className() === 'CommonModel' && draftId()) {
- <section class="content-controls">
- <header><div><strong>内容审核与归档</strong><span>专用云函数同步旧 ContentManage 状态与节点移动语义</span></div><b [class.recycled]="contentStatusValue() === -2">{{ contentStatusLabel(contentStatusValue()) }}</b></header>
- <label><span>审核状态</span><select [ngModel]="selectedContentStatus()" (ngModelChange)="selectedContentStatus.set($event)"><option [ngValue]="99">已审核(99)</option><option [ngValue]="0">待审核(0)</option><option [ngValue]="-1">未通过(-1)</option><option [ngValue]="-3">草稿(-3)</option></select></label>
- <button type="button" [disabled]="updatingContent()" (click)="applyContentAction('status')">更新状态</button>
- <label><span>目标节点 ID</span><input type="number" min="1" [ngModel]="targetContentNodeId()" (ngModelChange)="targetContentNodeId.set(+$event)" /></label>
- <button type="button" [disabled]="updatingContent() || !targetContentNodeId()" (click)="applyContentAction('move')">移动节点</button>
- <div class="content-recycle">@if (contentStatusValue() === -2) { <button type="button" [disabled]="updatingContent()" (click)="applyContentAction('recover')">从回收站恢复</button> } @else { <button class="danger-action" type="button" [disabled]="updatingContent()" (click)="applyContentAction('recycle')">移入回收站</button> }</div>
- @if (contentActionError()) { <div class="content-action-status error">{{ contentActionError() }}</div> }
- @if (contentActionMessage()) { <div class="content-action-status success">{{ contentActionMessage() }}</div> }
- </section>
- }
- @if (className() === 'Node') {
- <section class="content-controls node-controls">
- <header><div><strong>栏目结构与回收站</strong><span>专用云函数维护栏目编号、父子层级、同级防重与循环保护</span></div>@if (draftId()) { <b [class.recycled]="nodeStatusValue() === -2">{{ nodeStatusValue() === -2 ? '回收站' : '正常' }} · ID {{ draft()['nodeId'] }}</b> }</header>
- <label><span>父节点 ID <small>顶级栏目填 0</small></span><input type="number" min="0" [ngModel]="targetNodeParentId()" (ngModelChange)="targetNodeParentId.set(+$event)" /></label>
- @if (draftId()) {
- <button type="button" [disabled]="updatingNode()" (click)="applyNodeAction('move')">移动栏目</button>
- <div class="content-recycle">@if (nodeStatusValue() === -2) { <button type="button" [disabled]="updatingNode()" (click)="applyNodeAction('recover')">从回收站恢复</button> } @else { <button class="danger-action" type="button" [disabled]="updatingNode()" (click)="applyNodeAction('recycle')">移入回收站</button> }</div>
- @if (nodeActionError()) { <div class="content-action-status error">{{ nodeActionError() }}</div> }
- @if (nodeActionMessage()) { <div class="content-action-status success">{{ nodeActionMessage() }}</div> }
- }
- </section>
- }
- @if (className() === 'Special') {
- <section class="content-controls special-controls">
- <header><div><strong>专题结构</strong><span>专用云函数维护专题编号、全帐套名称/目录防重与父子循环保护</span></div>@if (draftId()) { <b>ID {{ draft()['specId'] }}</b> }</header>
- <label><span>父专题 ID <small>顶级专题填 0</small></span><input type="number" min="0" [ngModel]="targetSpecialParentId()" (ngModelChange)="targetSpecialParentId.set(+$event)" /></label>
- @if (draftId()) {
- <button type="button" [disabled]="updatingSpecial()" (click)="moveSpecial()">移动专题</button>
- @if (specialActionError()) { <div class="content-action-status error">{{ specialActionError() }}</div> }
- @if (specialActionMessage()) { <div class="content-action-status success">{{ specialActionMessage() }}</div> }
- }
- </section>
- }
- @if (className() === 'Guestcate') {
- <section class="content-controls guest-category-controls">
- <header><div><strong>留言 / 贴吧分类结构</strong><span>专用云函数维护原子 cateid、分类类型、父子循环和引用保护</span></div>@if (draftId()) { <b>ID {{ draft()['cateid'] }}</b> }</header>
- <label><span>分类用途</span><select [disabled]="!!draftId()" [ngModel]="guestCategoryType()" (ngModelChange)="guestCategoryType.set(+$event); targetGuestCategoryParentId.set(0)"><option [ngValue]="0">留言分类</option><option [ngValue]="1">贴吧版块</option></select></label>
- <label><span>父分类 ID <small>留言分类固定为 0</small></span><input type="number" min="0" [disabled]="guestCategoryType() === 0" [ngModel]="targetGuestCategoryParentId()" (ngModelChange)="targetGuestCategoryParentId.set(+$event)" /></label>
- </section>
- }
- @if (className() === 'ExamClass') {
- <section class="content-controls exam-class-controls">
- <header><div><strong>试题分类结构</strong><span>专用云函数维护原子分类编号、父子循环、名称防重和试题引用保护</span></div>@if (draftId()) { <b>ID {{ draft()['cId'] }}</b> }</header>
- <label><span>父分类 ID <small>顶级分类填 0</small></span><input type="number" min="0" [ngModel]="targetExamClassParentId()" (ngModelChange)="targetExamClassParentId.set(+$event)" /></label>
- <label><span>分类类型</span><select [ngModel]="examClassType()" (ngModelChange)="examClassType.set(+$event)"><option [ngValue]="1">答题类型</option><option [ngValue]="2">视频操作类型</option></select></label>
- <label><span>排序 <small>新建填 0 自动顺排</small></span><input type="number" min="0" [ngModel]="examClassOrder()" (ngModelChange)="examClassOrder.set(+$event)" /></label>
- </section>
- }
- @if (className() === 'ExamPoint') {
- <section class="content-controls exam-point-controls">
- <header><div><strong>考点结构</strong><span>专用云函数维护原子考点编号、父子循环、名称防重和下级引用保护</span></div>@if (draftId()) { <b>ID {{ draft()['id'] }}</b> }</header>
- <label><span>上级考点 ID <small>顶级考点填 0</small></span><input type="number" min="0" [ngModel]="targetExamPointParentId()" (ngModelChange)="targetExamPointParentId.set(+$event)" /></label>
- <label><span>排序 <small>新建填 0 自动顺排</small></span><input type="number" min="0" [ngModel]="examPointOrder()" (ngModelChange)="examPointOrder.set(+$event)" /></label>
- </section>
- }
- @if (className() === 'QuestionsKnowledge') {
- <section class="content-controls knowledge-controls">
- <header><div><strong>知识点结构</strong><span>专用云函数维护原子知识点编号、所属试题分类、父子循环和试题引用保护</span></div>@if (draftId()) { <b>ID {{ draft()['kId'] }}</b> }</header>
- <label><span>试题分类 ID</span><input type="number" min="1" [ngModel]="targetKnowledgeClassId()" (ngModelChange)="targetKnowledgeClassId.set(+$event)" /></label>
- <label><span>父知识点 ID <small>顶级知识点填 0</small></span><input type="number" min="0" [ngModel]="targetKnowledgeParentId()" (ngModelChange)="targetKnowledgeParentId.set(+$event)" /></label>
- <label><span>排序 <small>新建填 0 自动顺排</small></span><input type="number" min="0" [ngModel]="knowledgeOrder()" (ngModelChange)="knowledgeOrder.set(+$event)" /></label>
- </section>
- }
- @if (className() === 'ExTeacher') {
- <section class="content-controls exam-teacher-controls">
- <header><div><strong>考试教师资料</strong><span>专用云函数维护原子教师编号、创建管理员和任教试题分类归属</span></div>@if (draftId()) { <b>ID {{ draft()['id'] }}</b> }</header>
- <label><span>任教试题分类 ID <small>未指定填 0</small></span><input type="number" min="0" [ngModel]="targetTeacherClassId()" (ngModelChange)="targetTeacherClassId.set(+$event)" /></label>
- </section>
- }
- @if (className() === 'Datadiccategory') {
- <section class="content-controls dictionary-controls">
- <header><div><strong>数据字典分类</strong><span>专用云函数维护旧 DicCateID、帐套内名称防重和字典项引用保护</span></div>@if (draftId()) { <b>ID {{ draft()['diccateid'] }}</b> }</header>
- </section>
- }
- @if (className() === 'Datadic') {
- <section class="content-controls dictionary-controls">
- <header><div><strong>数据字典项</strong><span>专用云函数维护旧 DicID,并只允许挂到当前帐套真实存在的分类</span></div>@if (draftId()) { <b>ID {{ draft()['dicid'] }}</b> }</header>
- <label><span>所属字典分类</span><select [ngModel]="targetDictionaryCategoryId()" (ngModelChange)="targetDictionaryCategoryId.set(+$event)"><option [ngValue]="0">请选择分类</option>@for (category of dictionaryCategories(); track category['objectId']) { <option [ngValue]="dictionaryCategoryIdOf(category)">{{ category['categoryname'] }}({{ category['diccateid'] }}){{ category['isused'] === false ? ' · 已停用' : '' }}</option> }</select>@if (!dictionaryCategories().length) { <small>当前帐套尚无可用分类,请先从“数据字典分类”建立分类</small> }</label>
- </section>
- }
- @if (className() === 'GradeCate') {
- <section class="content-controls grade-category-controls">
- <header><div><strong>多级字典分类</strong><span>层级别名使用“|”或换行分隔,至少定义两级;专用云函数维护旧 CateID 和引用保护</span></div>@if (draftId()) { <b>ID {{ draft()['cateId'] }}</b> }</header>
- </section>
- }
- @if (className() === 'Grade') {
- <section class="content-controls grade-option-controls">
- <header><div><strong>多级字典选项</strong><span>新建时按父选项自动推导层级;编辑时保持原分类、父级和层级不变</span></div>@if (draftId()) { <b>ID {{ draft()['gradeId'] }} · 第 {{ draft()['grade'] }} 级</b> }</header>
- <label><span>所属多级字典分类</span><select [disabled]="!!draftId()" [ngModel]="targetGradeCategoryId()" (ngModelChange)="targetGradeCategoryId.set(+$event)"><option [ngValue]="0">请选择分类</option>@for (category of gradeCategories(); track category['objectId']) { <option [ngValue]="gradeCategoryIdOf(category)">{{ category['cateName'] }}({{ category['cateId'] }})</option> }</select></label>
- <label><span>父选项 ID <small>第一级填 0</small></span><input type="number" min="0" [disabled]="!!draftId()" [ngModel]="targetGradeParentId()" (ngModelChange)="targetGradeParentId.set(+$event)" /></label>
- </section>
- }
- @if (className() === 'Guestbook' && draftId()) {
- <section class="content-controls guestbook-controls">
- <header><div><strong>留言审核与管理员回复</strong><span>专用云函数维护旧 status、父留言关系和回复 GID</span></div><b [class.recycled]="selectedGuestbookStatus() === -2">{{ selectedGuestbookStatus() === 99 ? '已审核' : selectedGuestbookStatus() === -2 ? '回收站' : '待审核' }}</b></header>
- <button type="button" [disabled]="updatingGuestbook()" (click)="applyGuestbookAction('audit')">审核</button><button type="button" [disabled]="updatingGuestbook()" (click)="applyGuestbookAction('unaudit')">取消审核</button>
- <div class="content-recycle">@if (selectedGuestbookStatus() === -2) { <button type="button" [disabled]="updatingGuestbook()" (click)="applyGuestbookAction('recover')">从回收站恢复</button> } @else { <button class="danger-action" type="button" [disabled]="updatingGuestbook()" (click)="applyGuestbookAction('recycle')">移入回收站</button> }</div>
- <label><span>回复标题 <small>选填</small></span><input [ngModel]="guestbookReplyTitle()" (ngModelChange)="guestbookReplyTitle.set($event)" placeholder="[管理员回复]" /></label>
- <label><span>回复内容 <small>必填</small></span><textarea rows="5" [ngModel]="guestbookReplyContent()" (ngModelChange)="guestbookReplyContent.set($event)"></textarea></label>
- <button type="button" [disabled]="updatingGuestbook() || !guestbookReplyContent().trim()" (click)="replyGuestbook()">发布管理员回复</button>
- @if (guestbookActionError()) { <div class="content-action-status error">{{ guestbookActionError() }}</div> }
- @if (guestbookActionMessage()) { <div class="content-action-status success">{{ guestbookActionMessage() }}</div> }
- </section>
- }
- @if (className() === 'GuestBar' && draftId()) {
- <section class="content-controls bar-controls">
- <header><div><strong>贴吧帖子工作流</strong><span>审核、回收、精华、置顶/沉底和移动均由专用云函数维护</span></div><b [class.recycled]="draft()['status'] === -2">{{ draft()['status'] === -2 ? '回收站' : draft()['status'] === 99 ? '已审核' : '待审核' }}</b></header>
- <button type="button" [disabled]="updatingBar()" (click)="applyBarAction('audit')">审核</button><button type="button" [disabled]="updatingBar()" (click)="applyBarAction('unaudit')">取消审核</button>
- <button type="button" [disabled]="updatingBar()" (click)="applyBarAction('elite')">设为精华</button><button type="button" [disabled]="updatingBar()" (click)="applyBarAction('elite-undo')">取消精华</button>
- <button type="button" [disabled]="updatingBar()" (click)="applyBarAction('top-section')">版块置顶</button><button type="button" [disabled]="updatingBar()" (click)="applyBarAction('top-global')">全局置顶</button>
- <button type="button" [disabled]="updatingBar()" (click)="applyBarAction('bottom')">沉底</button><button type="button" [disabled]="updatingBar()" (click)="applyBarAction('top-undo')">取消排序</button>
- <label><span>目标贴吧分类 ID</span><input type="number" min="1" [ngModel]="targetBarCategoryId()" (ngModelChange)="targetBarCategoryId.set(+$event)" /></label><button type="button" [disabled]="updatingBar() || !targetBarCategoryId()" (click)="applyBarAction('move')">移动版块</button>
- <div class="content-recycle">@if (draft()['status'] === -2) { <button type="button" [disabled]="updatingBar()" (click)="applyBarAction('recover')">从回收站恢复</button> } @else { <button class="danger-action" type="button" [disabled]="updatingBar()" (click)="applyBarAction('recycle')">移入回收站</button> }</div>
- @if (barActionError()) { <div class="content-action-status error">{{ barActionError() }}</div> }
- @if (barActionMessage()) { <div class="content-action-status success">{{ barActionMessage() }}</div> }
- </section>
- }
- @if (className() === '_User' && draftId()) {
- <section class="user-controls">
- <header><div><strong>账号状态与用户组</strong><span>专用云函数会同步旧版 State / GroupID;停用账号会立即撤销会话</span></div><b [class.disabled]="draft()['isDisabled'] === true">{{ draft()['isDisabled'] === true ? '已停用' : '正常' }}</b></header>
- <label class="reason-field"><span>状态变更原因 <small>选填 · 最多 200 字</small></span><input [ngModel]="userActionReason()" (ngModelChange)="userActionReason.set($event)" maxlength="200" /></label>
- <div class="status-actions">@if (draft()['isDisabled'] === true) { <button type="button" [disabled]="updatingUser()" (click)="setUserDisabled(false)">解除停用</button> } @else { <button class="danger-action" type="button" [disabled]="updatingUser()" (click)="setUserDisabled(true)">停用并撤销会话</button> }</div>
- <label class="group-field"><span>用户组</span><select [ngModel]="selectedUserGroupId()" (ngModelChange)="selectedUserGroupId.set($event)"><option [ngValue]="0">请选择用户组</option>@for (group of userGroups(); track group['objectId']) { <option [ngValue]="groupIdOf(group)">{{ groupLabel(group) }}</option> }</select>@if (!userGroups().length) { <small>当前帐套尚无 Group 记录,请先在“全部数据类”中建立用户组</small> }</label>
- <button class="group-submit" type="button" [disabled]="updatingUser() || !selectedUserGroupId()" (click)="moveUserGroup()">保存用户组</button>
- @if (userActionError()) { <div class="user-action-status error">{{ userActionError() }}</div> }
- @if (userActionMessage()) { <div class="user-action-status success">{{ userActionMessage() }}</div> }
- </section>
- <section class="password-reset">
- <header><lucide-icon [img]="icons.KeyRound" [size]="18" /><div><strong>重置登录密码</strong><span>通过专用云函数写入,不进入通用资料字段</span></div></header>
- <label><span>新密码 <small>至少 8 位</small></span><input type="password" autocomplete="new-password" [ngModel]="newPassword()" (ngModelChange)="newPassword.set($event)" /></label>
- <label><span>确认密码</span><input type="password" autocomplete="new-password" [ngModel]="confirmPassword()" (ngModelChange)="confirmPassword.set($event)" /></label>
- @if (passwordError()) { <div class="password-status error">{{ passwordError() }}</div> }
- @if (passwordMessage()) { <div class="password-status success">{{ passwordMessage() }}</div> }
- <button type="button" [disabled]="resettingPassword()" (click)="resetPassword()">{{ resettingPassword() ? '重置中…' : '确认重置密码' }}</button>
- </section>
- }
- }
- </div>
- <footer><button type="button" (click)="closeEditor()">{{ schema()?.writable ? '取消' : '关闭' }}</button>@if (schema()?.writable) { <button class="primary" type="button" [disabled]="saving()" (click)="submitEditor()">{{ saving() ? '保存中…' : className() === '_User' && !draftId() ? '开通用户' : '保存记录' }}</button> }</footer>
- </aside>
- }
|