Compare commits
105
Commits
869d437485
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e7ccc1ba4 | ||
|
|
ec5dfcd82f | ||
|
|
7a59c65252 | ||
|
|
5d3358d844 | ||
|
|
eceddb5f7d | ||
|
|
376827ea97 | ||
|
|
024ecddfed | ||
|
|
51d5b8a2a1 | ||
|
|
374de05059 | ||
|
|
ee65fc7fd2 | ||
|
|
a254cbd3e9 | ||
|
|
f3dd1ec3b1 | ||
|
|
7fe26d3fff | ||
|
|
30ceab4615 | ||
|
|
d391b0fd00 | ||
|
|
304c59c8f3 | ||
|
|
b497eb4a59 | ||
|
|
561378312f | ||
|
|
a061ec86ce | ||
|
|
49f1212e33 | ||
|
|
a5386f72c6 | ||
|
|
ec582ac9f9 | ||
|
|
4f9347b6aa | ||
|
|
305896796c | ||
|
|
5afa7ab9df | ||
|
|
c2733017fd | ||
|
|
44a03fd5f4 | ||
|
|
beb898b1ab | ||
|
|
330df53195 | ||
|
|
8bb4b98fc9 | ||
|
|
4cd296c066 | ||
|
|
f97fd72bd0 | ||
|
|
ac84e991d1 | ||
|
|
0e4e4670dd | ||
|
|
c5bdbc0457 | ||
|
|
3e48fdf61e | ||
|
|
1970db0b56 | ||
|
|
5f31c58df2 | ||
|
|
4a119358ca | ||
|
|
6928127bfa | ||
|
|
7885f42cff | ||
|
|
7c33482be7 | ||
|
|
7bdf7cf018 | ||
|
|
384d2b7aa0 | ||
|
|
4868de2cda | ||
|
|
9e8913ea0a | ||
|
|
f5e9420266 | ||
|
|
79909f449e | ||
|
|
d07cffe99e | ||
|
|
2c7262770b | ||
|
|
fac4a714e0 | ||
|
|
7a8279f8e7 | ||
|
|
fbb53c6890 | ||
|
|
7faa2d53d8 | ||
|
|
b124795d84 | ||
|
|
f53cdd4dc8 | ||
|
|
4c562428b4 | ||
|
|
ef7b5939ba | ||
|
|
6bcfbfb252 | ||
|
|
1dfcc38a83 | ||
|
|
8e74b60570 | ||
|
|
3ce5bfc20c | ||
|
|
b56b9bbe37 | ||
|
|
ed38dec314 | ||
|
|
3595935475 | ||
|
|
e6d9ff49a2 | ||
|
|
55423403a6 | ||
|
|
8c4aabb9a4 | ||
|
|
dcd306b428 | ||
|
|
bcba0f598a | ||
|
|
b1c3d86e44 | ||
|
|
6db34646ed | ||
|
|
6f29e3d865 | ||
|
|
7d78a33a9d | ||
|
|
6d01e7489a | ||
|
|
09b1e535e4 | ||
|
|
00ae62f62b | ||
|
|
41366e7fc4 | ||
|
|
d40906b4a3 | ||
|
|
8ad5ef9aff | ||
|
|
ba32e0ef9c | ||
|
|
02246c7ec8 | ||
|
|
6c3c0f7c6d | ||
|
|
451b04160b | ||
|
|
0007e78bfe | ||
|
|
f6fde18857 | ||
|
|
f14e6bd88d | ||
|
|
93e83e1835 | ||
|
|
3c07ef7a8d | ||
|
|
f80ca1e27d | ||
|
|
1549d5b25d | ||
|
|
030baa511e | ||
|
|
e30de6562c | ||
|
|
39aed0d430 | ||
|
|
427968a721 | ||
|
|
42cc64106d | ||
|
|
35bb96e82f | ||
|
|
5b16bc6e15 | ||
|
|
4e62b325d2 | ||
|
|
5c6b422866 | ||
|
|
1c95f94bca | ||
|
|
324bcfdadc | ||
|
|
df12f51199 | ||
|
|
4740078135 | ||
|
|
9c70d7929a |
No files matched your search
+8
-4
@@ -7,7 +7,7 @@
|
|||||||
/.workbuddy/
|
/.workbuddy/
|
||||||
|
|
||||||
# Project planning, internal docs, and local-only development files
|
# Project planning, internal docs, and local-only development files
|
||||||
/.aksrt-chrome-profile/
|
/.jio-chrome-profile/
|
||||||
/.specs/
|
/.specs/
|
||||||
/docs/
|
/docs/
|
||||||
|
|
||||||
@@ -23,13 +23,17 @@ assets/css/routes/docs/*
|
|||||||
!assets/css/routes/docs/toolbar-search-enhancements.css
|
!assets/css/routes/docs/toolbar-search-enhancements.css
|
||||||
|
|
||||||
# Frontend build output and installed dependencies.
|
# Frontend build output and installed dependencies.
|
||||||
# assets/admin/build is deploy-only: build it on the target with
|
# assets/admin/build and assets/creator-studio/build are deploy-only:
|
||||||
|
# build them on the target with
|
||||||
# cd assets/admin && npm ci && npm run build
|
# cd assets/admin && npm ci && npm run build
|
||||||
|
# cd assets/creator-studio && npm ci && npm run build
|
||||||
/assets/admin/node_modules/
|
/assets/admin/node_modules/
|
||||||
/assets/admin/build/*
|
/assets/admin/build/
|
||||||
!/assets/admin/build/.gitkeep
|
|
||||||
assets/admin/.vite-temp/
|
assets/admin/.vite-temp/
|
||||||
|
|
||||||
|
/assets/creator-studio/node_modules/
|
||||||
|
/assets/creator-studio/build/
|
||||||
|
|
||||||
# PHP installed dependencies
|
# PHP installed dependencies
|
||||||
/vendor/*
|
/vendor/*
|
||||||
!/vendor/.gitkeep
|
!/vendor/.gitkeep
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
/**
|
/**
|
||||||
* 404 template.
|
* 404 template.
|
||||||
*
|
*
|
||||||
* @package AKSRT
|
* @package Jio
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
get_header();
|
get_header();
|
||||||
|
|
||||||
$search_placeholder = function_exists('aksrt_get_search_placeholder') ? aksrt_get_search_placeholder() : __('开启精彩搜索', 'aksrt');
|
$search_placeholder = function_exists('jio_get_search_placeholder') ? jio_get_search_placeholder() : __('开启精彩搜索', 'jio');
|
||||||
$hot_keywords = function_exists('aksrt_get_search_hot_keywords') ? aksrt_get_search_hot_keywords() : [];
|
$hot_keywords = function_exists('jio_get_search_hot_keywords') ? jio_get_search_hot_keywords() : [];
|
||||||
|
|
||||||
$hot_query = new WP_Query([
|
$hot_query = new WP_Query([
|
||||||
'post_type' => 'post',
|
'post_type' => 'post',
|
||||||
@@ -39,33 +39,33 @@ $top_cats = get_terms([
|
|||||||
<i class="fa fas fa-compass"></i>
|
<i class="fa fas fa-compass"></i>
|
||||||
</p>
|
</p>
|
||||||
<p class="error-hero__code" id="error-title">404</p>
|
<p class="error-hero__code" id="error-title">404</p>
|
||||||
<h1 class="error-hero__title"><?php esc_html_e('页面未找到', 'aksrt'); ?></h1>
|
<h1 class="error-hero__title"><?php esc_html_e('页面未找到', 'jio'); ?></h1>
|
||||||
<p class="error-hero__desc">
|
<p class="error-hero__desc">
|
||||||
<?php esc_html_e('你访问的页面不存在或已被删除。试试搜索,或者从下方常用入口继续浏览。', 'aksrt'); ?>
|
<?php esc_html_e('你访问的页面不存在或已被删除。试试搜索,或者从下方常用入口继续浏览。', 'jio'); ?>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<?php if (function_exists('aksrt_render_admin_quick_link')) { aksrt_render_admin_quick_link('page-display/other', __('404 页设置', 'aksrt')); } ?>
|
<?php if (function_exists('jio_render_admin_quick_link')) { jio_render_admin_quick_link('page-display/other', __('404 页设置', 'jio')); } ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form role="search" method="get" class="error-hero__search" action="<?php echo esc_url(home_url('/')); ?>">
|
<form role="search" method="get" class="error-hero__search" action="<?php echo esc_url(home_url('/')); ?>">
|
||||||
<span class="error-hero__search-icon" aria-hidden="true">
|
<span class="error-hero__search-icon" aria-hidden="true">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||||
</span>
|
</span>
|
||||||
<label class="screen-reader-text" for="error-search-input"><?php esc_html_e('搜索', 'aksrt'); ?></label>
|
<label class="screen-reader-text" for="error-search-input"><?php esc_html_e('搜索', 'jio'); ?></label>
|
||||||
<input type="search" id="error-search-input" name="s" placeholder="<?php echo esc_attr($search_placeholder); ?>" autocomplete="off">
|
<input type="search" id="error-search-input" name="s" placeholder="<?php echo esc_attr($search_placeholder); ?>" autocomplete="off">
|
||||||
<button type="submit" class="btn-primary btn-sm"><?php esc_html_e('搜索', 'aksrt'); ?></button>
|
<button type="submit" class="btn-primary btn-sm"><?php esc_html_e('搜索', 'jio'); ?></button>
|
||||||
</form>
|
</form>
|
||||||
<?php aksrt_render_search_hot_keywords($hot_keywords, 'aksrt-search-hot-keywords'); ?>
|
<?php jio_render_search_hot_keywords($hot_keywords, 'jio-search-hot-keywords'); ?>
|
||||||
|
|
||||||
<div class="error-hero__actions">
|
<div class="error-hero__actions">
|
||||||
<a href="<?php echo esc_url(home_url('/')); ?>" class="btn-primary"><?php esc_html_e('返回首页', 'aksrt'); ?></a>
|
<a href="<?php echo esc_url(home_url('/')); ?>" class="btn-primary"><?php esc_html_e('返回首页', 'jio'); ?></a>
|
||||||
<a href="javascript:history.back()" class="btn-outline"><?php esc_html_e('返回上一页', 'aksrt'); ?></a>
|
<a href="javascript:history.back()" class="btn-outline"><?php esc_html_e('返回上一页', 'jio'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if (!empty($top_cats) && !is_wp_error($top_cats)) : ?>
|
<?php if (!empty($top_cats) && !is_wp_error($top_cats)) : ?>
|
||||||
<section class="error-section" aria-label="<?php esc_attr_e('热门分类', 'aksrt'); ?>">
|
<section class="error-section" aria-label="<?php esc_attr_e('热门分类', 'jio'); ?>">
|
||||||
<h2 class="error-section__title"><?php esc_html_e('热门分类', 'aksrt'); ?></h2>
|
<h2 class="error-section__title"><?php esc_html_e('热门分类', 'jio'); ?></h2>
|
||||||
<div class="error-section__chips">
|
<div class="error-section__chips">
|
||||||
<?php foreach ($top_cats as $cat) : ?>
|
<?php foreach ($top_cats as $cat) : ?>
|
||||||
<a class="error-section__chip" href="<?php echo esc_url(get_category_link($cat->term_id)); ?>">
|
<a class="error-section__chip" href="<?php echo esc_url(get_category_link($cat->term_id)); ?>">
|
||||||
@@ -78,8 +78,8 @@ $top_cats = get_terms([
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($hot_query->have_posts()) : ?>
|
<?php if ($hot_query->have_posts()) : ?>
|
||||||
<section class="error-section" aria-label="<?php esc_attr_e('热门内容', 'aksrt'); ?>">
|
<section class="error-section" aria-label="<?php esc_attr_e('热门内容', 'jio'); ?>">
|
||||||
<h2 class="error-section__title"><?php esc_html_e('热门内容', 'aksrt'); ?></h2>
|
<h2 class="error-section__title"><?php esc_html_e('热门内容', 'jio'); ?></h2>
|
||||||
<ul class="error-section__list">
|
<ul class="error-section__list">
|
||||||
<?php while ($hot_query->have_posts()) : $hot_query->the_post(); ?>
|
<?php while ($hot_query->have_posts()) : $hot_query->the_post(); ?>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# AKSRT WordPress Theme
|
# Jio WordPress Theme
|
||||||
|
|
||||||
> **版本**: 3.4.0-dev | **架构**: 原生 PHP + WordPress (非 headless)
|
> **版本**: 3.4.0-dev | **架构**: 原生 PHP + WordPress (非 headless)
|
||||||
> **要求**: WordPress >= 6.4 | PHP >= 8.1
|
> **要求**: WordPress >= 6.4 | PHP >= 8.1
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
## 项目概述
|
## 项目概述
|
||||||
|
|
||||||
AKSRT 是一款现代化的 WordPress 主题,采用**原生 WordPress 主题架构**开发(PHP + HTML + CSS + JavaScript),不依赖 REST API 进行前端渲染。
|
Jio 是一款现代化的 WordPress 主题,采用**原生 WordPress 主题架构**开发(PHP + HTML + CSS + JavaScript),不依赖 REST API 进行前端渲染。
|
||||||
|
|
||||||
### 核心特性
|
### 核心特性
|
||||||
|
|
||||||
@@ -22,14 +22,14 @@ AKSRT 是一款现代化的 WordPress 主题,采用**原生 WordPress 主题
|
|||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
AKSRT-theme/
|
Jio/
|
||||||
├── functions.php # 入口:常量 + Composer autoload + bootstrap + 单例 init
|
├── functions.php # 入口:常量 + Composer autoload + bootstrap + 单例 init
|
||||||
├── style.css # WordPress 主题声明头
|
├── style.css # WordPress 主题声明头
|
||||||
├── composer.json # 第三方依赖 + 主题 PSR-4(AKSRT\ → src/)
|
├── composer.json # 第三方依赖 + 主题 PSR-4(Jio\ → src/)
|
||||||
│
|
│
|
||||||
├── *.php # WP 核心模板保留根目录
|
├── *.php # WP 核心模板保留根目录
|
||||||
│
|
│
|
||||||
├── src/ # Composer PSR-4 类型根(namespace AKSRT\)
|
├── src/ # Composer PSR-4 类型根(namespace Jio\)
|
||||||
│ ├── Admin/ # SPA 编排 + 域控制器 + Schema + Native ops
|
│ ├── Admin/ # SPA 编排 + 域控制器 + Schema + Native ops
|
||||||
│ ├── Auth/ # 登录 / OAuth / 审核 / 邮箱
|
│ ├── Auth/ # 登录 / OAuth / 审核 / 邮箱
|
||||||
│ ├── Content/ # 下载 token / 付费内容 / VIP / 可见性
|
│ ├── Content/ # 下载 token / 付费内容 / VIP / 可见性
|
||||||
@@ -89,7 +89,7 @@ AKSRT-theme/
|
|||||||
|
|
||||||
## 安装方式
|
## 安装方式
|
||||||
|
|
||||||
主题类型(`AKSRT\…`)通过 Composer PSR-4 从 `src/` 自动加载;`vendor/` 不进 Git。**部署或更新主题后必须在主题根目录生成 autoload**,否则会出现 `Class "AKSRT\Core\DomainModel" not found` 等致命错误。
|
主题类型(`Jio\…`)通过 Composer PSR-4 从 `src/` 自动加载;`vendor/` 不进 Git。**部署或更新主题后必须在主题根目录生成 autoload**,否则会出现 `Class "Jio\Core\DomainModel" not found` 等致命错误。
|
||||||
|
|
||||||
### 前置:Composer(必做)
|
### 前置:Composer(必做)
|
||||||
|
|
||||||
@@ -106,16 +106,16 @@ composer dump-autoload -o
|
|||||||
确认:
|
确认:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php -r "require 'vendor/autoload.php'; echo class_exists('AKSRT\\\\Core\\\\DomainModel') ? \"ok\\n\" : \"missing\\n\";"
|
php -r "require 'vendor/autoload.php'; echo class_exists('Jio\\\\Core\\\\DomainModel') ? \"ok\\n\" : \"missing\\n\";"
|
||||||
```
|
```
|
||||||
|
|
||||||
应输出 `ok`。若 PHP 开启了 OPcache,重载 php-fpm / 重启 PHP 后再访问站点。
|
应输出 `ok`。若 PHP 开启了 OPcache,重载 php-fpm / 重启 PHP 后再访问站点。
|
||||||
|
|
||||||
### 方式一:直接上传(推荐)
|
### 方式一:直接上传(推荐)
|
||||||
|
|
||||||
1. 将整个 `AKSRT-theme` 文件夹上传到 `wp-content/themes/`
|
1. 将整个 `Jio` 文件夹上传到 `wp-content/themes/`
|
||||||
2. 在服务器主题目录执行上一节的 `composer install`(若打包时已带上完整 `vendor/` 且已 `dump-autoload`,可跳过)
|
2. 在服务器主题目录执行上一节的 `composer install`(若打包时已带上完整 `vendor/` 且已 `dump-autoload`,可跳过)
|
||||||
3. WordPress 后台 → **外观** → **主题** → 找到 **AKSRT** → **激活**
|
3. WordPress 后台 → **外观** → **主题** → 找到 **Jio** → **激活**
|
||||||
|
|
||||||
### 方式二:ZIP 打包上传
|
### 方式二:ZIP 打包上传
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ php -r "require 'vendor/autoload.php'; echo class_exists('AKSRT\\\\Core\\\\Domai
|
|||||||
SSH 进入主题目录后:
|
SSH 进入主题目录后:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/wp-content/themes/AKsrt-theme # 以实际路径为准
|
cd /path/to/wp-content/themes/Jio # 以实际路径为准
|
||||||
composer install --no-dev --optimize-autoloader
|
composer install --no-dev --optimize-autoloader
|
||||||
# 或仅刷新映射:composer dump-autoload -o
|
# 或仅刷新映射:composer dump-autoload -o
|
||||||
# 可选:reload php-fpm / 清 OPcache
|
# 可选:reload php-fpm / 清 OPcache
|
||||||
@@ -150,9 +150,9 @@ composer install --no-dev --optimize-autoloader
|
|||||||
|
|
||||||
### 2. 后台配置路径
|
### 2. 后台配置路径
|
||||||
|
|
||||||
WordPress 后台左侧 **Admin Bar** → ⚡ **AKSRT 主题设置**
|
WordPress 后台左侧 **Admin Bar** → ⚡ **Jio 主题设置**
|
||||||
|
|
||||||
或直接访问:`/wp-admin/admin.php?page=aksrt-settings`
|
或直接访问:`/wp-admin/admin.php?page=jio-settings`
|
||||||
|
|
||||||
### 3. 当前后台域
|
### 3. 当前后台域
|
||||||
|
|
||||||
@@ -167,13 +167,13 @@ WordPress 后台左侧 **Admin Bar** → ⚡ **AKSRT 主题设置**
|
|||||||
### 4. 推广、邀请与佣金(用户中心 → 收益)
|
### 4. 推广、邀请与佣金(用户中心 → 收益)
|
||||||
|
|
||||||
- **推广链接**:用户中心「收益」页展示带 `?ref=` 的推广链接、二维码(可下载 PNG)与推广海报(按「推广返佣」设置中的海报文案生成)。归因模式(首触/末触)在「支付&付费 → 推广返佣 → 推广归因模式」配置。
|
- **推广链接**:用户中心「收益」页展示带 `?ref=` 的推广链接、二维码(可下载 PNG)与推广海报(按「推广返佣」设置中的海报文案生成)。归因模式(首触/末触)在「支付&付费 → 推广返佣 → 推广归因模式」配置。
|
||||||
- **一次性邀请码**:后台「用户 → 邀请码管理」三视图运营页——列表(状态筛选/搜索/批量删除/排序/分页/点击复制)、添加(系统自动生成:数量/标识/四类奖励[经验值、积分、余额、VIP]/高级选项自定义位数;或 Zibll 兼容文本导入:一行一码 `邀请码 奖励 标识`,奖励参数串 `points=20&balance=30&vip=1&vip_time=5&level_integral=100`,分隔符可自定义)、导出(状态筛选 + 文本文档[可回流导入]/Excel表格 双格式)。注册门控模式在「用户&互动 → 登录注册 → 邀请码注册」。消费为原子操作,并发注册不会超发;开启注册验证时奖励延迟到邮箱验证后发放。
|
- **一次性邀请码**:后台「用户 → 邀请码管理」三视图运营页——列表(状态筛选/搜索/批量删除/排序/分页/点击复制)、添加(系统自动生成:数量/标识/四类奖励[经验值、积分、余额、VIP]/高级选项自定义位数;或行格式文本导入:一行一码 `邀请码 奖励 标识`,奖励参数串 `points=20&balance=30&vip=1&vip_time=5&level_integral=100`,分隔符可自定义)、导出(状态筛选 + 文本文档[可回流导入]/Excel表格 双格式)。注册门控模式在「用户&互动 → 登录注册 → 邀请码注册」。消费为原子操作,并发注册不会超发;开启注册验证时奖励延迟到邮箱验证后发放。
|
||||||
- **佣金**:订单支付成功后按推广者等级分档比例记录到佣金账本,经「结算观察期」后自动结算入钱包,支持提现与退款联动回滚。「推广码禁用后停止返佣」开关默认关闭。
|
- **佣金**:订单支付成功后按推广者等级分档比例记录到佣金账本,经「结算观察期」后自动结算入钱包,支持提现与退款联动回滚。「推广码禁用后停止返佣」开关默认关闭。
|
||||||
|
|
||||||
### 5. 部署注意事项
|
### 5. 部署注意事项
|
||||||
|
|
||||||
- **佣金结算时效**:结算依赖小时级 wp-cron,并有收益页访问触发的懒结算兜底。高流量站点建议禁用 `WP_CRON_LOCK` 场景下的默认 WP-Cron,改用系统 crontab 定时请求 `wp-cron.php`,保证结算与过期任务准时执行。
|
- **佣金结算时效**:结算依赖小时级 wp-cron,并有收益页访问触发的懒结算兜底。高流量站点建议禁用 `WP_CRON_LOCK` 场景下的默认 WP-Cron,改用系统 crontab 定时请求 `wp-cron.php`,保证结算与过期任务准时执行。
|
||||||
- **反代 / CDN 与 IP 风控**:主题默认按 Cloudflare → 通用代理头顺序识别真实 IP。若需收紧伪造风险,添加 `add_filter('aksrt_trusted_proxies', fn() => ['10.0.0.0/8', '173.245.48.0/20'])`(填入你的代理/CDN 网段);名单非空时仅当 `REMOTE_ADDR` 命中名单才采纳转发头,否则直接使用 `REMOTE_ADDR`(推广奖励的 IP 日上限、验证码等均依赖该函数)。
|
- **反代 / CDN 与 IP 风控**:主题默认按 Cloudflare → 通用代理头顺序识别真实 IP。若需收紧伪造风险,添加 `add_filter('jio_trusted_proxies', fn() => ['10.0.0.0/8', '173.245.48.0/20'])`(填入你的代理/CDN 网段);名单非空时仅当 `REMOTE_ADDR` 命中名单才采纳转发头,否则直接使用 `REMOTE_ADDR`(推广奖励的 IP 日上限、验证码等均依赖该函数)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -181,20 +181,20 @@ WordPress 后台左侧 **Admin Bar** → ⚡ **AKSRT 主题设置**
|
|||||||
|
|
||||||
### Settings Facade 分层
|
### Settings Facade 分层
|
||||||
|
|
||||||
- `AKSRT_Settings` 仍是主题设置的**唯一静态 facade**,外部调用方式保持 `AKSRT_Settings::*`
|
- `JIO_Settings` 仍是主题设置的**唯一静态 facade**,外部调用方式保持 `JIO_Settings::*`
|
||||||
- 具体运行时 getter 已按领域拆分到 `inc/classes/settings/`,例如:
|
- 具体运行时 getter 已按领域拆分到 `inc/classes/settings/`,例如:
|
||||||
- `class-aksrt-settings-theme-search.php`
|
- `class-jio-settings-theme-search.php`
|
||||||
- `class-aksrt-settings-post-display.php`
|
- `class-jio-settings-post-display.php`
|
||||||
- `class-aksrt-settings-home-cms.php`
|
- `class-jio-settings-home-cms.php`
|
||||||
- `class-aksrt-settings-user-runtime.php`
|
- `class-jio-settings-user-runtime.php`
|
||||||
- `class-aksrt-settings-integration.php`
|
- `class-jio-settings-integration.php`
|
||||||
- `class-aksrt-settings-display-runtime.php`
|
- `class-jio-settings-display-runtime.php`
|
||||||
- `class-aksrt-settings-extension-runtime.php`
|
- `class-jio-settings-extension-runtime.php`
|
||||||
- `class-aksrt-settings-credits-runtime.php`
|
- `class-jio-settings-credits-runtime.php`
|
||||||
- `class-aksrt-settings-category-runtime.php`
|
- `class-jio-settings-category-runtime.php`
|
||||||
- `class-aksrt-settings-capabilities.php`
|
- `class-jio-settings-capabilities.php`
|
||||||
- `inc/bootstrap.php` 会先加载 `settings/class-aksrt-settings.php`,再按领域加载这些 delegate 类
|
- `inc/bootstrap.php` 会先加载 `settings/class-jio-settings.php`,再按领域加载这些 delegate 类
|
||||||
- 新增设置运行时逻辑时,优先放入对应 settings 子类;仅把聚合输出、统一入口和跨域装配保留在 `AKSRT_Settings`
|
- 新增设置运行时逻辑时,优先放入对应 settings 子类;仅把聚合输出、统一入口和跨域装配保留在 `JIO_Settings`
|
||||||
|
|
||||||
### 添加新辅助函数
|
### 添加新辅助函数
|
||||||
|
|
||||||
@@ -202,14 +202,14 @@ WordPress 后台左侧 **Admin Bar** → ⚡ **AKSRT 主题设置**
|
|||||||
|
|
||||||
### 添加新页面模板
|
### 添加新页面模板
|
||||||
|
|
||||||
将 `page-{name}.php` 放入 `templates/pages/`,并在 `class-aksrt-templates.php` 中注册。
|
将 `page-{name}.php` 放入 `templates/pages/`,并在 `class-jio-templates.php` 中注册。
|
||||||
|
|
||||||
### CSS 组织规范
|
### CSS 组织规范
|
||||||
|
|
||||||
| 目录 | 用途 | 加载场景 |
|
| 目录 | 用途 | 加载场景 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
| `css/frontend/` | 前端公共样式 | 所有前台页面 |
|
| `css/frontend/` | 前端公共样式 | 所有前台页面 |
|
||||||
| `css/admin/` | 后台管理样式 | 仅 AKSRT 设置页面 |
|
| `css/admin/` | 后台管理样式 | 仅 Jio 设置页面 |
|
||||||
| `css/shared/` | 共用组件样式 | 前后端按需加载(如验证码) |
|
| `css/shared/` | 共用组件样式 | 前后端按需加载(如验证码) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+26
-26
@@ -2,9 +2,9 @@
|
|||||||
/**
|
/**
|
||||||
* Resource Archive Template
|
* Resource Archive Template
|
||||||
*
|
*
|
||||||
* Virtual list at /resources/ — ordinary posts marked with _aksrt_is_resource.
|
* Virtual list at /resources/ — ordinary posts marked with _jio_is_resource.
|
||||||
*
|
*
|
||||||
* @package AKSRT
|
* @package Jio
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
@@ -25,18 +25,18 @@ if (!in_array($current_price, ['all', 'free', 'paid'], true)) {
|
|||||||
$price_min = isset($_GET['price_min']) && $_GET['price_min'] !== '' ? (float) $_GET['price_min'] : '';
|
$price_min = isset($_GET['price_min']) && $_GET['price_min'] !== '' ? (float) $_GET['price_min'] : '';
|
||||||
$price_max = isset($_GET['price_max']) && $_GET['price_max'] !== '' ? (float) $_GET['price_max'] : '';
|
$price_max = isset($_GET['price_max']) && $_GET['price_max'] !== '' ? (float) $_GET['price_max'] : '';
|
||||||
|
|
||||||
$archive_url = function_exists('aksrt_resource_archive_url') ? aksrt_resource_archive_url() : home_url('/resources/');
|
$archive_url = function_exists('jio_resource_archive_url') ? jio_resource_archive_url() : home_url('/resources/');
|
||||||
|
|
||||||
$sort_options = [
|
$sort_options = [
|
||||||
'date' => __('最新', 'aksrt'),
|
'date' => __('最新', 'jio'),
|
||||||
'downloads' => __('热门下载', 'aksrt'),
|
'downloads' => __('热门下载', 'jio'),
|
||||||
'views' => __('阅读最多', 'aksrt'),
|
'views' => __('阅读最多', 'jio'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$price_options = [
|
$price_options = [
|
||||||
'all' => __('全部', 'aksrt'),
|
'all' => __('全部', 'jio'),
|
||||||
'free' => __('免费', 'aksrt'),
|
'free' => __('免费', 'jio'),
|
||||||
'paid' => __('付费', 'aksrt'),
|
'paid' => __('付费', 'jio'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$preserve = [];
|
$preserve = [];
|
||||||
@@ -59,18 +59,18 @@ $has_filter = $current_orderby !== 'date' || $current_price !== 'all' || $price_
|
|||||||
<main id="main-content" class="site-main container" tabindex="-1">
|
<main id="main-content" class="site-main container" tabindex="-1">
|
||||||
<header class="resource-archive-header page-cover">
|
<header class="resource-archive-header page-cover">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<?php if (function_exists('aksrt_breadcrumbs')) : ?>
|
<?php if (function_exists('jio_breadcrumbs')) : ?>
|
||||||
<nav class="resource-archive-header__breadcrumbs" aria-label="<?php esc_attr_e('面包屑', 'aksrt'); ?>">
|
<nav class="resource-archive-header__breadcrumbs" aria-label="<?php esc_attr_e('面包屑', 'jio'); ?>">
|
||||||
<?php aksrt_breadcrumbs(); ?>
|
<?php jio_breadcrumbs(); ?>
|
||||||
</nav>
|
</nav>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<h1 class="resource-archive-header__title"><?php esc_html_e('资源', 'aksrt'); ?></h1>
|
<h1 class="resource-archive-header__title"><?php esc_html_e('资源', 'jio'); ?></h1>
|
||||||
<p class="resource-archive-header__sub">
|
<p class="resource-archive-header__sub">
|
||||||
<?php
|
<?php
|
||||||
global $wp_query;
|
global $wp_query;
|
||||||
/* translators: %s: resource count */
|
/* translators: %s: resource count */
|
||||||
printf(esc_html__('共 %s 个资源', 'aksrt'), esc_html(number_format_i18n((int) $wp_query->found_posts)));
|
printf(esc_html__('共 %s 个资源', 'jio'), esc_html(number_format_i18n((int) $wp_query->found_posts)));
|
||||||
?>
|
?>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,9 +79,9 @@ $has_filter = $current_orderby !== 'date' || $current_price !== 'all' || $price_
|
|||||||
<div class="content-wrap">
|
<div class="content-wrap">
|
||||||
<div class="content-layout content-layout--no-sidebar">
|
<div class="content-layout content-layout--no-sidebar">
|
||||||
<div class="content-layout__primary resource-archive">
|
<div class="content-layout__primary resource-archive">
|
||||||
<nav class="resource-archive__toolbar" aria-label="<?php esc_attr_e('筛选', 'aksrt'); ?>">
|
<nav class="resource-archive__toolbar" aria-label="<?php esc_attr_e('筛选', 'jio'); ?>">
|
||||||
<div class="resource-archive__filter-group">
|
<div class="resource-archive__filter-group">
|
||||||
<span class="resource-archive__filter-label"><?php esc_html_e('排序', 'aksrt'); ?></span>
|
<span class="resource-archive__filter-label"><?php esc_html_e('排序', 'jio'); ?></span>
|
||||||
<ul class="resource-archive__sort">
|
<ul class="resource-archive__sort">
|
||||||
<?php foreach ($sort_options as $key => $label) :
|
<?php foreach ($sort_options as $key => $label) :
|
||||||
$args = $preserve;
|
$args = $preserve;
|
||||||
@@ -105,7 +105,7 @@ $has_filter = $current_orderby !== 'date' || $current_price !== 'all' || $price_
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="resource-archive__filter-group">
|
<div class="resource-archive__filter-group">
|
||||||
<span class="resource-archive__filter-label"><?php esc_html_e('价格', 'aksrt'); ?></span>
|
<span class="resource-archive__filter-label"><?php esc_html_e('价格', 'jio'); ?></span>
|
||||||
<ul class="resource-archive__sort">
|
<ul class="resource-archive__sort">
|
||||||
<?php foreach ($price_options as $key => $label) :
|
<?php foreach ($price_options as $key => $label) :
|
||||||
$args = $preserve;
|
$args = $preserve;
|
||||||
@@ -139,43 +139,43 @@ $has_filter = $current_orderby !== 'date' || $current_price !== 'all' || $price_
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<label class="resource-archive__price-input">
|
<label class="resource-archive__price-input">
|
||||||
<span><?php esc_html_e('最低', 'aksrt'); ?></span>
|
<span><?php esc_html_e('最低', 'jio'); ?></span>
|
||||||
<input type="number" name="price_min" min="0" step="1" value="<?php echo esc_attr((string) $price_min); ?>">
|
<input type="number" name="price_min" min="0" step="1" value="<?php echo esc_attr((string) $price_min); ?>">
|
||||||
</label>
|
</label>
|
||||||
<span class="resource-archive__price-sep">—</span>
|
<span class="resource-archive__price-sep">—</span>
|
||||||
<label class="resource-archive__price-input">
|
<label class="resource-archive__price-input">
|
||||||
<span><?php esc_html_e('最高', 'aksrt'); ?></span>
|
<span><?php esc_html_e('最高', 'jio'); ?></span>
|
||||||
<input type="number" name="price_max" min="0" step="1" value="<?php echo esc_attr((string) $price_max); ?>">
|
<input type="number" name="price_max" min="0" step="1" value="<?php echo esc_attr((string) $price_max); ?>">
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" class="btn-primary btn-sm"><?php esc_html_e('应用', 'aksrt'); ?></button>
|
<button type="submit" class="btn-primary btn-sm"><?php esc_html_e('应用', 'jio'); ?></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<?php if ($has_filter) : ?>
|
<?php if ($has_filter) : ?>
|
||||||
<a class="resource-archive__reset" href="<?php echo esc_url($archive_url); ?>">
|
<a class="resource-archive__reset" href="<?php echo esc_url($archive_url); ?>">
|
||||||
<i class="fa fas fa-times-circle" aria-hidden="true"></i>
|
<i class="fa fas fa-times-circle" aria-hidden="true"></i>
|
||||||
<?php esc_html_e('清除筛选', 'aksrt'); ?>
|
<?php esc_html_e('清除筛选', 'jio'); ?>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<?php if (have_posts()) : ?>
|
<?php if (have_posts()) : ?>
|
||||||
<?php
|
<?php
|
||||||
$render_config = aksrt_resolve_post_list_render_config([
|
$render_config = jio_resolve_post_list_render_config([
|
||||||
'context' => 'archive',
|
'context' => 'archive',
|
||||||
'overrides' => ['type' => 'card'],
|
'overrides' => ['type' => 'card'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
aksrt_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list']));
|
jio_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list']));
|
||||||
aksrt_render_pagination(null, [
|
jio_render_pagination(null, [
|
||||||
'query_context' => 'archive',
|
'query_context' => 'archive',
|
||||||
'render_config' => $render_config,
|
'render_config' => $render_config,
|
||||||
]);
|
]);
|
||||||
?>
|
?>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<div class="no-results">
|
<div class="no-results">
|
||||||
<p><?php esc_html_e('暂无资源。', 'aksrt'); ?></p>
|
<p><?php esc_html_e('暂无资源。', 'jio'); ?></p>
|
||||||
<?php if ($has_filter) : ?>
|
<?php if ($has_filter) : ?>
|
||||||
<p><a class="btn-outline btn-sm" href="<?php echo esc_url($archive_url); ?>"><?php esc_html_e('清除筛选条件', 'aksrt'); ?></a></p>
|
<p><a class="btn-outline btn-sm" href="<?php echo esc_url($archive_url); ?>"><?php esc_html_e('清除筛选条件', 'jio'); ?></a></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
+19
-19
@@ -2,7 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* Generic archive template.
|
* Generic archive template.
|
||||||
*
|
*
|
||||||
* @package AKSRT
|
* @package Jio
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
@@ -11,31 +11,31 @@ get_header();
|
|||||||
|
|
||||||
global $wp_query;
|
global $wp_query;
|
||||||
|
|
||||||
$render_config = function_exists('aksrt_archive_get_render_config')
|
$render_config = function_exists('jio_archive_get_render_config')
|
||||||
? aksrt_archive_get_render_config(['context' => 'archive'])
|
? jio_archive_get_render_config(['context' => 'archive'])
|
||||||
: ['context' => 'archive', 'mode' => 'list'];
|
: ['context' => 'archive', 'mode' => 'list'];
|
||||||
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
|
$has_sidebar = function_exists('jio_archive_has_sidebar')
|
||||||
? aksrt_archive_has_sidebar()
|
? jio_archive_has_sidebar()
|
||||||
: (function_exists('aksrt_is_show_sidebar') ? aksrt_is_show_sidebar() : false);
|
: (function_exists('jio_is_show_sidebar') ? jio_is_show_sidebar() : false);
|
||||||
|
|
||||||
$archive_description = trim(wp_strip_all_tags((string) get_the_archive_description()));
|
$archive_description = trim(wp_strip_all_tags((string) get_the_archive_description()));
|
||||||
$archive_summary = '';
|
$archive_summary = '';
|
||||||
if (is_author()) {
|
if (is_author()) {
|
||||||
$author = get_queried_object();
|
$author = get_queried_object();
|
||||||
if ($author instanceof WP_User) {
|
if ($author instanceof WP_User) {
|
||||||
$archive_summary = sprintf(__('作者:%s', 'aksrt'), $author->display_name);
|
$archive_summary = sprintf(__('作者:%s', 'jio'), $author->display_name);
|
||||||
}
|
}
|
||||||
} elseif ($archive_description !== '') {
|
} elseif ($archive_description !== '') {
|
||||||
$archive_summary = $archive_description;
|
$archive_summary = $archive_description;
|
||||||
} elseif (isset($wp_query->found_posts)) {
|
} elseif (isset($wp_query->found_posts)) {
|
||||||
$archive_summary = sprintf(
|
$archive_summary = sprintf(
|
||||||
esc_html(_n('共 %d 篇内容', '共 %d 篇内容', (int) $wp_query->found_posts, 'aksrt')),
|
esc_html(_n('共 %d 篇内容', '共 %d 篇内容', (int) $wp_query->found_posts, 'jio')),
|
||||||
(int) $wp_query->found_posts
|
(int) $wp_query->found_posts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (function_exists('aksrt_archive_open_shell')) {
|
if (function_exists('jio_archive_open_shell')) {
|
||||||
aksrt_archive_open_shell([
|
jio_archive_open_shell([
|
||||||
'main_class' => 'site-main container',
|
'main_class' => 'site-main container',
|
||||||
'has_sidebar' => $has_sidebar,
|
'has_sidebar' => $has_sidebar,
|
||||||
'page_kind' => 'archive',
|
'page_kind' => 'archive',
|
||||||
@@ -45,16 +45,16 @@ if (function_exists('aksrt_archive_open_shell')) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<div class="content-main content-main--archive">
|
<div class="content-main content-main--archive">
|
||||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
<?php if (function_exists('jio_breadcrumbs')) { jio_breadcrumbs(); } ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
ob_start();
|
ob_start();
|
||||||
if (function_exists('aksrt_render_admin_quick_link')) {
|
if (function_exists('jio_render_admin_quick_link')) {
|
||||||
aksrt_render_admin_quick_link('post-list/list', __('列表样式设置', 'aksrt'));
|
jio_render_admin_quick_link('post-list/list', __('列表样式设置', 'jio'));
|
||||||
}
|
}
|
||||||
$admin_link = trim((string) ob_get_clean());
|
$admin_link = trim((string) ob_get_clean());
|
||||||
get_template_part('templates/parts/page-header', null, [
|
get_template_part('templates/parts/page-header', null, [
|
||||||
'eyebrow' => __('内容归档', 'aksrt'),
|
'eyebrow' => __('内容归档', 'jio'),
|
||||||
'title' => get_the_archive_title(),
|
'title' => get_the_archive_title(),
|
||||||
'desc' => $archive_summary,
|
'desc' => $archive_summary,
|
||||||
'extra' => $admin_link,
|
'extra' => $admin_link,
|
||||||
@@ -62,19 +62,19 @@ if (function_exists('aksrt_archive_open_shell')) {
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
if (function_exists('aksrt_archive_render_stream')) {
|
if (function_exists('jio_archive_render_stream')) {
|
||||||
aksrt_archive_render_stream(null, $render_config, [
|
jio_archive_render_stream(null, $render_config, [
|
||||||
'page_kind' => 'archive',
|
'page_kind' => 'archive',
|
||||||
'query_context' => 'archive',
|
'query_context' => 'archive',
|
||||||
'stream_class' => 'content-stream--archive',
|
'stream_class' => 'content-stream--archive',
|
||||||
'empty_text' => __('暂无文章', 'aksrt'),
|
'empty_text' => __('暂无文章', 'jio'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
if (function_exists('aksrt_archive_close_shell')) {
|
if (function_exists('jio_archive_close_shell')) {
|
||||||
aksrt_archive_close_shell(['has_sidebar' => $has_sidebar]);
|
jio_archive_close_shell(['has_sidebar' => $has_sidebar]);
|
||||||
} else {
|
} else {
|
||||||
echo '</div></div>';
|
echo '</div></div>';
|
||||||
if ($has_sidebar) { get_sidebar(); }
|
if ($has_sidebar) { get_sidebar(); }
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const COMPONENT_STYLES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
async function extractStyles() {
|
async function extractStyles() {
|
||||||
console.log('\n[AKSRT] 开始提取 NaiveUI 组件样式...\n')
|
console.log('\n[Jio] 开始提取 NaiveUI 组件样式...\n')
|
||||||
|
|
||||||
const allCss = []
|
const allCss = []
|
||||||
let successCount = 0
|
let successCount = 0
|
||||||
@@ -137,9 +137,9 @@ async function extractStyles() {
|
|||||||
// 静默跳过的后果是组件样式整体缺失(取色器曾因此没有尺寸和边框,糊成一团),
|
// 静默跳过的后果是组件样式整体缺失(取色器曾因此没有尺寸和边框,糊成一团),
|
||||||
// 而页面不报错,只能靠肉眼发现。
|
// 而页面不报错,只能靠肉眼发现。
|
||||||
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
|
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
|
||||||
console.error(`[AKSRT] 样式路径不存在,已跳过: ${componentPath}`)
|
console.error(`[Jio] 样式路径不存在,已跳过: ${componentPath}`)
|
||||||
} else {
|
} else {
|
||||||
console.error(`[AKSRT] 样式提取异常,已跳过: ${componentPath}: ${err && err.message}`)
|
console.error(`[Jio] 样式提取异常,已跳过: ${componentPath}: ${err && err.message}`)
|
||||||
}
|
}
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ async function extractStyles() {
|
|||||||
const cssOutput = allCss.join('\n\n')
|
const cssOutput = allCss.join('\n\n')
|
||||||
|
|
||||||
if (!cssOutput.trim()) {
|
if (!cssOutput.trim()) {
|
||||||
console.warn('[AKSRT] 警告:未提取到任何 NaiveUI 样式!')
|
console.warn('[Jio] 警告:未提取到任何 NaiveUI 样式!')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ ${cssOutput}`
|
|||||||
|
|
||||||
await writeFile(cssFile, fullContent, 'utf-8')
|
await writeFile(cssFile, fullContent, 'utf-8')
|
||||||
|
|
||||||
console.log(`[AKSRT] NaiveUI 样式提取完成:`)
|
console.log(`[Jio] NaiveUI 样式提取完成:`)
|
||||||
console.log(` 成功: ${successCount} 个组件`)
|
console.log(` 成功: ${successCount} 个组件`)
|
||||||
console.log(` 跳过: ${failCount} 个组件`)
|
console.log(` 跳过: ${failCount} 个组件`)
|
||||||
console.log(` 输出: ${cssFile}`)
|
console.log(` 输出: ${cssFile}`)
|
||||||
@@ -176,6 +176,6 @@ ${cssOutput}`
|
|||||||
|
|
||||||
// 执行
|
// 执行
|
||||||
extractStyles().catch((err) => {
|
extractStyles().catch((err) => {
|
||||||
console.error('[AKSRT] 样式提取失败:', err)
|
console.error('[Jio] 样式提取失败:', err)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>AKSRT 主题设置</title>
|
<title>Jio 主题设置</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="aksrt-admin-app"></div>
|
<div id="jio-admin-app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "aksrt-admin",
|
"name": "jio-admin",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "aksrt-admin",
|
"name": "jio-admin",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"name":"aksrt-admin","version":"1.0.0","private":true,"type":"module","scripts":{"dev":"vite","build":"vite build && node extract-naive-styles.mjs","build:public":"node ../../tools/build-public-assets.mjs","build:pay-charts":"node ../../tools/build-pay-charts.mjs","preview":"vite preview"},"dependencies":{"@codemirror/lang-css":"^6.3.1","@codemirror/lang-html":"^6.4.11","@codemirror/lang-javascript":"^6.2.5","@codemirror/theme-one-dark":"^6.1.3","codemirror":"^6.0.2","naive-ui":"^2.40.0","pinia":"^2.2.0","vue":"^3.5.0","vue-router":"^4.4.0","vuedraggable":"^4.1.0"},"devDependencies":{"@vitejs/plugin-vue":"^5.1.0","echarts":"^6.1.0","esbuild":"^0.25.12","vite":"^6.0.0"},"allowScripts":{"esbuild@0.25.12":true,"vue-demi@0.14.10":true}}
|
{"name":"jio-admin","version":"1.0.0","private":true,"type":"module","scripts":{"dev":"vite","build":"vite build && node extract-naive-styles.mjs","build:public":"node ../../tools/build-public-assets.mjs","build:pay-charts":"node ../../tools/build-pay-charts.mjs","preview":"vite preview"},"dependencies":{"@codemirror/lang-css":"^6.3.1","@codemirror/lang-html":"^6.4.11","@codemirror/lang-javascript":"^6.2.5","@codemirror/theme-one-dark":"^6.1.3","codemirror":"^6.0.2","naive-ui":"^2.40.0","pinia":"^2.2.0","vue":"^3.5.0","vue-router":"^4.4.0","vuedraggable":"^4.1.0"},"devDependencies":{"@vitejs/plugin-vue":"^5.1.0","echarts":"^6.1.0","esbuild":"^0.25.12","vite":"^6.0.0"},"allowScripts":{"esbuild@0.25.12":true,"vue-demi@0.14.10":true}}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
+45
-45
@@ -6,9 +6,9 @@
|
|||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<n-notification-provider>
|
<n-notification-provider>
|
||||||
<n-layout v-if="!isMobile" class="aksrt-admin-layout" has-sider>
|
<n-layout v-if="!isMobile" class="jio-admin-layout" has-sider>
|
||||||
<n-layout-sider
|
<n-layout-sider
|
||||||
class="aksrt-admin-sider"
|
class="jio-admin-sider"
|
||||||
bordered
|
bordered
|
||||||
collapse-mode="width"
|
collapse-mode="width"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
@@ -19,16 +19,16 @@
|
|||||||
@collapse="collapsed = true"
|
@collapse="collapsed = true"
|
||||||
@expand="collapsed = false"
|
@expand="collapsed = false"
|
||||||
>
|
>
|
||||||
<div class="aksrt-logo">
|
<div class="jio-logo">
|
||||||
<span class="logo-icon">AK</span>
|
<span class="logo-icon">AK</span>
|
||||||
<span v-if="!collapsed" class="aksrt-logo__copy">
|
<span v-if="!collapsed" class="jio-logo__copy">
|
||||||
<span class="logo-text">AKSRT</span>
|
<span class="logo-text">Jio</span>
|
||||||
<span class="aksrt-logo__version">v{{ themeVersion }}</span>
|
<span class="jio-logo__version">v{{ themeVersion }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-menu
|
<n-menu
|
||||||
class="aksrt-admin-menu"
|
class="jio-admin-menu"
|
||||||
:value="activeMenuKey"
|
:value="activeMenuKey"
|
||||||
:collapsed="collapsed"
|
:collapsed="collapsed"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
@@ -37,39 +37,39 @@
|
|||||||
/>
|
/>
|
||||||
</n-layout-sider>
|
</n-layout-sider>
|
||||||
|
|
||||||
<n-layout :native-scrollbar="false" class="aksrt-admin-content">
|
<n-layout :native-scrollbar="false" class="jio-admin-content">
|
||||||
<div class="aksrt-admin-content__inner">
|
<div class="jio-admin-content__inner">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition name="fade-slide" mode="out-in">
|
<transition name="fade-slide" mode="out-in">
|
||||||
<div class="aksrt-page-wrapper" :key="route.fullPath">
|
<div class="jio-page-wrapper" :key="route.fullPath">
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
|
|
||||||
<div v-if="pageError" class="aksrt-error-fallback">
|
<div v-if="pageError" class="jio-error-fallback">
|
||||||
<p class="aksrt-error-title">页面渲染异常</p>
|
<p class="jio-error-title">页面渲染异常</p>
|
||||||
<p class="aksrt-error-msg">{{ pageError }}</p>
|
<p class="jio-error-msg">{{ pageError }}</p>
|
||||||
<n-button size="small" @click="pageError = null">重试</n-button>
|
<n-button size="small" @click="pageError = null">重试</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
|
|
||||||
<div v-else class="aksrt-admin-mobile">
|
<div v-else class="jio-admin-mobile">
|
||||||
<MobileTopBar :title="currentPageTitle" @toggle="drawerActive = true" />
|
<MobileTopBar :title="currentPageTitle" @toggle="drawerActive = true" />
|
||||||
|
|
||||||
<n-drawer v-model:show="drawerActive" placement="left" :width="280">
|
<n-drawer v-model:show="drawerActive" placement="left" :width="280">
|
||||||
<n-drawer-content :native-scrollbar="false" body-content-style="padding:0;">
|
<n-drawer-content :native-scrollbar="false" body-content-style="padding:0;">
|
||||||
<div class="aksrt-logo aksrt-logo--drawer">
|
<div class="jio-logo jio-logo--drawer">
|
||||||
<span class="logo-icon">AK</span>
|
<span class="logo-icon">AK</span>
|
||||||
<span class="aksrt-logo__copy">
|
<span class="jio-logo__copy">
|
||||||
<span class="logo-text">AKSRT</span>
|
<span class="logo-text">Jio</span>
|
||||||
<span class="aksrt-logo__version">v{{ themeVersion }}</span>
|
<span class="jio-logo__version">v{{ themeVersion }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<n-menu
|
<n-menu
|
||||||
class="aksrt-admin-menu"
|
class="jio-admin-menu"
|
||||||
:value="activeMenuKey"
|
:value="activeMenuKey"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
@update:value="handleMenuClick"
|
@update:value="handleMenuClick"
|
||||||
@@ -77,18 +77,18 @@
|
|||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
|
|
||||||
<div class="aksrt-admin-mobile__body">
|
<div class="jio-admin-mobile__body">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition name="fade-slide" mode="out-in">
|
<transition name="fade-slide" mode="out-in">
|
||||||
<div class="aksrt-page-wrapper" :key="route.fullPath">
|
<div class="jio-page-wrapper" :key="route.fullPath">
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
|
|
||||||
<div v-if="pageError" class="aksrt-error-fallback">
|
<div v-if="pageError" class="jio-error-fallback">
|
||||||
<p class="aksrt-error-title">页面渲染异常</p>
|
<p class="jio-error-title">页面渲染异常</p>
|
||||||
<p class="aksrt-error-msg">{{ pageError }}</p>
|
<p class="jio-error-msg">{{ pageError }}</p>
|
||||||
<n-button size="small" @click="pageError = null">重试</n-button>
|
<n-button size="small" @click="pageError = null">重试</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,7 +113,7 @@ const collapsed = ref(false)
|
|||||||
const pageError = ref(null)
|
const pageError = ref(null)
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false)
|
||||||
const drawerActive = ref(false)
|
const drawerActive = ref(false)
|
||||||
const themeVersion = window.aksrtAdminData?.themeVersion || window.aksrtAdminData?.version || '9.0'
|
const themeVersion = window.jioAdminData?.themeVersion || window.jioAdminData?.version || '9.0'
|
||||||
const MOBILE_BREAKPOINT = 960
|
const MOBILE_BREAKPOINT = 960
|
||||||
|
|
||||||
const adminThemeOverrides = {
|
const adminThemeOverrides = {
|
||||||
@@ -184,7 +184,7 @@ onBeforeUnmount(() => {
|
|||||||
provide('collapsed', collapsed)
|
provide('collapsed', collapsed)
|
||||||
|
|
||||||
onErrorCaptured((err) => {
|
onErrorCaptured((err) => {
|
||||||
console.error('[AKSRT] 组件渲染错误:', err)
|
console.error('[Jio] 组件渲染错误:', err)
|
||||||
pageError.value = err?.message || String(err)
|
pageError.value = err?.message || String(err)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@@ -224,7 +224,7 @@ const menuOptions = ADMIN_SECTIONS.filter((section) => !section.hidden).map((sec
|
|||||||
children: section.children.map((child) => ({
|
children: section.children.map((child) => ({
|
||||||
label: child.secondaryEntry ? `${child.label} · 次级` : child.label,
|
label: child.secondaryEntry ? `${child.label} · 次级` : child.label,
|
||||||
key: child.key,
|
key: child.key,
|
||||||
props: child.secondaryEntry ? { class: 'aksrt-menu-item--secondary' } : undefined,
|
props: child.secondaryEntry ? { class: 'jio-menu-item--secondary' } : undefined,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -254,7 +254,7 @@ const activeMenuKey = computed(() => {
|
|||||||
return ADMIN_SECTIONS[0]?.children?.[0]?.key || 'global-common'
|
return ADMIN_SECTIONS[0]?.children?.[0]?.key || 'global-common'
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentPageTitle = computed(() => route.meta?.title || 'AKSRT')
|
const currentPageTitle = computed(() => route.meta?.title || 'Jio')
|
||||||
|
|
||||||
function handleMenuClick(key) {
|
function handleMenuClick(key) {
|
||||||
if (keyToPath[key]) {
|
if (keyToPath[key]) {
|
||||||
@@ -267,17 +267,17 @@ function handleMenuClick(key) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-logo {
|
.jio-logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
gap: 11px;
|
gap: 11px;
|
||||||
padding: 0 18px;
|
padding: 0 18px;
|
||||||
border-bottom: 1px solid var(--aksrt-color-border);
|
border-bottom: 1px solid var(--jio-color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-logo--drawer {
|
.jio-logo--drawer {
|
||||||
height: 64px;
|
height: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,14 +289,14 @@ function handleMenuClick(key) {
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
flex: 0 0 28px;
|
flex: 0 0 28px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background: var(--aksrt-color-primary);
|
background: var(--jio-color-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-logo__copy {
|
.jio-logo__copy {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
@@ -306,56 +306,56 @@ function handleMenuClick(key) {
|
|||||||
.logo-text {
|
.logo-text {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
letter-spacing: .08em;
|
letter-spacing: .08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-logo__version {
|
.jio-logo__version {
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
letter-spacing: .02em;
|
letter-spacing: .02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-admin-content__inner {
|
.jio-admin-content__inner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1236px;
|
max-width: 1236px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 26px 28px 42px;
|
padding: 26px 28px 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-admin-mobile {
|
.jio-admin-mobile {
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-admin-mobile__body {
|
.jio-admin-mobile__body {
|
||||||
padding: 14px 12px 32px;
|
padding: 14px 12px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-wrapper {
|
.jio-page-wrapper {
|
||||||
min-height: calc(100vh - 120px);
|
min-height: calc(100vh - 120px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-error-fallback {
|
.jio-error-fallback {
|
||||||
padding: 48px 20px;
|
padding: 48px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-error-title {
|
.jio-error-title {
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #d03050;
|
color: #d03050;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-error-msg {
|
.jio-error-msg {
|
||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.aksrt-admin-content__inner {
|
.jio-admin-content__inner {
|
||||||
padding: 22px 20px 38px;
|
padding: 22px 20px 38px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* AKSRT Admin API Client
|
* Jio Admin API Client
|
||||||
*
|
*
|
||||||
* Communicates with the WordPress REST API at /wp-json/aksrt/v1
|
* Communicates with the WordPress REST API at /wp-json/jio/v1
|
||||||
* (or index.php?rest_route=/aksrt/v1 when pretty permalinks are off).
|
* (or index.php?rest_route=/jio/v1 when pretty permalinks are off).
|
||||||
* Methods are organized into domain modules while the client keeps the shared
|
* Methods are organized into domain modules while the client keeps the shared
|
||||||
* request pipeline and schema-domain normalization contract.
|
* request pipeline and schema-domain normalization contract.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { apiMethods } from './modules/index.js'
|
import { apiMethods } from './modules/index.js'
|
||||||
|
|
||||||
const API_ROOT = window.aksrtAdminData?.apiUrl || '/wp-json/aksrt/v1'
|
const API_ROOT = window.jioAdminData?.apiUrl || '/wp-json/jio/v1'
|
||||||
const NONCE = window.aksrtAdminData?.nonce || ''
|
const NONCE = window.jioAdminData?.nonce || ''
|
||||||
|
|
||||||
function createIdempotencyKey() {
|
function createIdempotencyKey() {
|
||||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||||
@@ -34,11 +34,11 @@ function normalizeDomainKey(domain) {
|
|||||||
/**
|
/**
|
||||||
* Join REST base + endpoint correctly for both pretty and plain permalinks.
|
* Join REST base + endpoint correctly for both pretty and plain permalinks.
|
||||||
*
|
*
|
||||||
* Pretty: https://site/wp-json/aksrt/v1 + /settings/schema/global/common
|
* Pretty: https://site/wp-json/jio/v1 + /settings/schema/global/common
|
||||||
* → https://site/wp-json/aksrt/v1/settings/schema/global/common
|
* → https://site/wp-json/jio/v1/settings/schema/global/common
|
||||||
*
|
*
|
||||||
* Plain: https://site/index.php?rest_route=/aksrt/v1 + /settings/schema/global/common
|
* Plain: https://site/index.php?rest_route=/jio/v1 + /settings/schema/global/common
|
||||||
* → https://site/index.php?rest_route=/aksrt/v1/settings/schema/global/common
|
* → https://site/index.php?rest_route=/jio/v1/settings/schema/global/common
|
||||||
*
|
*
|
||||||
* @param {string} baseUrl
|
* @param {string} baseUrl
|
||||||
* @param {string} endpoint path starting with /, may include ?query
|
* @param {string} endpoint path starting with /, may include ?query
|
||||||
@@ -56,7 +56,7 @@ function joinRestUrl(baseUrl, endpoint) {
|
|||||||
path = '/' + path
|
path = '/' + path
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = String(baseUrl || '').trim() || '/wp-json/aksrt/v1'
|
const base = String(baseUrl || '').trim() || '/wp-json/jio/v1'
|
||||||
|
|
||||||
// Plain permalinks: rest_route query mode
|
// Plain permalinks: rest_route query mode
|
||||||
if (base.includes('rest_route=')) {
|
if (base.includes('rest_route=')) {
|
||||||
@@ -68,11 +68,11 @@ function joinRestUrl(baseUrl, endpoint) {
|
|||||||
return base.replace(/\/?$/, '') + path + (extraQuery ? (base.includes('?') ? '&' : '?') + extraQuery : '')
|
return base.replace(/\/?$/, '') + path + (extraQuery ? (base.includes('?') ? '&' : '?') + extraQuery : '')
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseRoute = (u.searchParams.get('rest_route') || '/aksrt/v1').replace(/\/$/, '') || '/aksrt/v1'
|
const baseRoute = (u.searchParams.get('rest_route') || '/jio/v1').replace(/\/$/, '') || '/jio/v1'
|
||||||
let fullRoute
|
let fullRoute
|
||||||
if (path === '/') {
|
if (path === '/') {
|
||||||
fullRoute = baseRoute
|
fullRoute = baseRoute
|
||||||
} else if (path.startsWith('/aksrt/')) {
|
} else if (path.startsWith('/jio/')) {
|
||||||
fullRoute = path
|
fullRoute = path
|
||||||
} else {
|
} else {
|
||||||
fullRoute = baseRoute + path
|
fullRoute = baseRoute + path
|
||||||
@@ -110,7 +110,7 @@ class ApiClient {
|
|||||||
async request(endpointOrConfig, options = {}) {
|
async request(endpointOrConfig, options = {}) {
|
||||||
// Dual-signature support:
|
// Dual-signature support:
|
||||||
// request('/users/1/restrictions')
|
// request('/users/1/restrictions')
|
||||||
// request({ url: '/aksrt/v1/users/1/restrictions', method: 'GET' })
|
// request({ url: '/jio/v1/users/1/restrictions', method: 'GET' })
|
||||||
let endpoint, method, headers, params, body, rest
|
let endpoint, method, headers, params, body, rest
|
||||||
|
|
||||||
if (typeof endpointOrConfig === 'object' && endpointOrConfig !== null) {
|
if (typeof endpointOrConfig === 'object' && endpointOrConfig !== null) {
|
||||||
@@ -134,10 +134,10 @@ class ApiClient {
|
|||||||
delete rest.data; delete rest.headers
|
delete rest.data; delete rest.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpoint.startsWith('/wp-json/aksrt/v1')) {
|
if (endpoint.startsWith('/wp-json/jio/v1')) {
|
||||||
endpoint = endpoint.replace('/wp-json/aksrt/v1', '') || '/'
|
endpoint = endpoint.replace('/wp-json/jio/v1', '') || '/'
|
||||||
} else if (endpoint.startsWith('/aksrt/v1')) {
|
} else if (endpoint.startsWith('/jio/v1')) {
|
||||||
endpoint = endpoint.replace('/aksrt/v1', '') || '/'
|
endpoint = endpoint.replace('/jio/v1', '') || '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = joinRestUrl(this.baseUrl, endpoint)
|
let url = joinRestUrl(this.baseUrl, endpoint)
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ export const globalApiMethods = {
|
|||||||
getFloatingButtons() { return this.request('/settings/floating-buttons') },
|
getFloatingButtons() { return this.request('/settings/floating-buttons') },
|
||||||
saveFloatingButtons(data) { return this.request('/settings/floating-buttons', { method: 'POST', body: JSON.stringify(data) }) },
|
saveFloatingButtons(data) { return this.request('/settings/floating-buttons', { method: 'POST', body: JSON.stringify(data) }) },
|
||||||
|
|
||||||
getFriendLinks() { return this.request('/friend-links') },
|
|
||||||
addFriendLink(data) { return this.request('/friend-links', { method: 'POST', body: JSON.stringify(data) }) },
|
|
||||||
deleteFriendLink(id) { return this.request(`/friend-links/${id}`, { method: 'DELETE' }) },
|
|
||||||
reorderFriendLinks(ids) { return this.request('/friend-links/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) },
|
|
||||||
|
|
||||||
getCmsFeatures() { return this.request('/settings/cms-features') },
|
getCmsFeatures() { return this.request('/settings/cms-features') },
|
||||||
saveCmsFeatures(cms_features) { return this.request('/settings/cms-features', { method: 'POST', body: JSON.stringify({ cms_features }) }) },
|
saveCmsFeatures(cms_features) { return this.request('/settings/cms-features', { method: 'POST', body: JSON.stringify({ cms_features }) }) },
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 工单管理端 REST(命名空间 aksrt/v1/admin)。
|
* 工单管理端 REST(命名空间 jio/v1/admin)。
|
||||||
*
|
*
|
||||||
* TicketRestAdmin 把三块字典 CRUD(工单状态 / 部门 / 预设回复)挂在
|
* TicketRestAdmin 把三块字典 CRUD(工单状态 / 部门 / 预设回复)挂在
|
||||||
* aksrt/v1/admin 下,全部要求 manage_options。api 客户端的 baseUrl 只到
|
* jio/v1/admin 下,全部要求 manage_options。api 客户端的 baseUrl 只到
|
||||||
* aksrt/v1,因此这里必须用 /aksrt/ 全路径形式(joinRestUrl 的
|
* jio/v1,因此这里必须用 /jio/ 全路径形式(joinRestUrl 的
|
||||||
* startsWith('/aksrt/') 分支会原样拼接,不再追加 base 前缀)。
|
* startsWith('/jio/') 分支会原样拼接,不再追加 base 前缀)。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ADMIN = '/aksrt/v1/admin/tickets'
|
const ADMIN = '/jio/v1/admin/tickets'
|
||||||
|
|
||||||
export const ticketsApiMethods = {
|
export const ticketsApiMethods = {
|
||||||
/* ---------------- 工单状态 ---------------- */
|
/* ---------------- 工单状态 ---------------- */
|
||||||
|
|||||||
@@ -27,8 +27,4 @@ export const usersApiMethods = {
|
|||||||
adjustUserCredits(id, asset, delta, remark) {
|
adjustUserCredits(id, asset, delta, remark) {
|
||||||
return this.request(`/assets/accounts/${id}/adjust`, { method: 'POST', body: JSON.stringify({ asset, delta, remark }) })
|
return this.request(`/assets/accounts/${id}/adjust`, { method: 'POST', body: JSON.stringify({ asset, delta, remark }) })
|
||||||
},
|
},
|
||||||
getCreditLog(params = {}) {
|
|
||||||
const qs = new URLSearchParams(params).toString()
|
|
||||||
return this.request('/assets/transactions' + (qs ? `?${qs}` : ''))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-deferred-page">
|
<div class="jio-deferred-page">
|
||||||
<div class="aksrt-deferred-page__icon">
|
<div class="jio-deferred-page__icon">
|
||||||
<FaIcon :icon="resolvedIcon" :size="48" />
|
<FaIcon :icon="resolvedIcon" :size="48" />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="aksrt-deferred-page__title">{{ resolvedTitle }}</h2>
|
<h2 class="jio-deferred-page__title">{{ resolvedTitle }}</h2>
|
||||||
<p class="aksrt-deferred-page__desc">{{ resolvedDesc }}</p>
|
<p class="jio-deferred-page__desc">{{ resolvedDesc }}</p>
|
||||||
<div class="aksrt-deferred-page__meta">
|
<div class="jio-deferred-page__meta">
|
||||||
<n-tag size="small" type="info">{{ resolvedStatus }}</n-tag>
|
<n-tag size="small" type="info">{{ resolvedStatus }}</n-tag>
|
||||||
<n-tag v-if="resolvedPhase" size="small" type="warning">Phase {{ resolvedPhase }}</n-tag>
|
<n-tag v-if="resolvedPhase" size="small" type="warning">Phase {{ resolvedPhase }}</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="resolvedDependsOn.length" class="aksrt-deferred-page__depends">
|
<div v-if="resolvedDependsOn.length" class="jio-deferred-page__depends">
|
||||||
<span class="aksrt-deferred-page__label">依赖</span>
|
<span class="jio-deferred-page__label">依赖</span>
|
||||||
<div class="aksrt-deferred-page__depends-list">
|
<div class="jio-deferred-page__depends-list">
|
||||||
<n-tag v-for="item in resolvedDependsOn" :key="item" size="small" type="default">{{ item }}</n-tag>
|
<n-tag v-for="item in resolvedDependsOn" :key="item" size="small" type="default">{{ item }}</n-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="resolvedReason" class="aksrt-deferred-page__reason">
|
<div v-if="resolvedReason" class="jio-deferred-page__reason">
|
||||||
<span class="aksrt-deferred-page__label">原因</span>
|
<span class="jio-deferred-page__label">原因</span>
|
||||||
<p>{{ resolvedReason }}</p>
|
<p>{{ resolvedReason }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="resolvedFeatures && resolvedFeatures.length" class="aksrt-deferred-page__features">
|
<div v-if="resolvedFeatures && resolvedFeatures.length" class="jio-deferred-page__features">
|
||||||
<div
|
<div
|
||||||
v-for="(feat, i) in resolvedFeatures"
|
v-for="(feat, i) in resolvedFeatures"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="aksrt-deferred-page__feature"
|
class="jio-deferred-page__feature"
|
||||||
>
|
>
|
||||||
<FaIcon icon="fa fas fa-check-circle" :size="14" />
|
<FaIcon icon="fa fas fa-check-circle" :size="14" />
|
||||||
<span>{{ feat }}</span>
|
<span>{{ feat }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-deferred-page__badge">
|
<div class="jio-deferred-page__badge">
|
||||||
<n-tag type="info" size="small">即将推出</n-tag>
|
<n-tag type="info" size="small">即将推出</n-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +78,7 @@ const resolvedFeatures = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-deferred-page {
|
.jio-deferred-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -88,75 +88,75 @@ const resolvedFeatures = computed(() => {
|
|||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__icon {
|
.jio-deferred-page__icon {
|
||||||
color: var(--aksrt-color-border-strong);
|
color: var(--jio-color-border-strong);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__title {
|
.jio-deferred-page__title {
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__desc {
|
.jio-deferred-page__desc {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__meta {
|
.jio-deferred-page__meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__depends,
|
.jio-deferred-page__depends,
|
||||||
.aksrt-deferred-page__reason {
|
.jio-deferred-page__reason {
|
||||||
width: min(520px, 100%);
|
width: min(520px, 100%);
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__label {
|
.jio-deferred-page__label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__depends-list {
|
.jio-deferred-page__depends-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__reason p {
|
.jio-deferred-page__reason p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__features {
|
.jio-deferred-page__features {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__feature {
|
.jio-deferred-page__feature {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-deferred-page__feature :deep(i) {
|
.jio-deferred-page__feature :deep(i) {
|
||||||
color: #52c41a;
|
color: #52c41a;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-empty-state">
|
<div class="jio-empty-state">
|
||||||
<div class="aksrt-empty-state__icon">
|
<div class="jio-empty-state__icon">
|
||||||
<i :class="icon" style="font-size: 48px;"></i>
|
<i :class="icon" style="font-size: 48px;"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="aksrt-empty-state__title">{{ title }}</h3>
|
<h3 class="jio-empty-state__title">{{ title }}</h3>
|
||||||
<p class="aksrt-empty-state__desc">{{ desc }}</p>
|
<p class="jio-empty-state__desc">{{ desc }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-empty-state {
|
.jio-empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -26,22 +26,22 @@ defineProps({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-empty-state__icon {
|
.jio-empty-state__icon {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: var(--aksrt-color-border-strong);
|
color: var(--jio-color-border-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-empty-state__title {
|
.jio-empty-state__title {
|
||||||
margin: 0 0 8px;
|
margin: 0 0 8px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-empty-state__desc {
|
.jio-empty-state__desc {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="aksrt-mobile-topbar">
|
<header class="jio-mobile-topbar">
|
||||||
<button
|
<button
|
||||||
class="aksrt-mobile-topbar__menu"
|
class="jio-mobile-topbar__menu"
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="打开菜单"
|
aria-label="打开菜单"
|
||||||
@click="$emit('toggle')"
|
@click="$emit('toggle')"
|
||||||
@@ -13,35 +13,35 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="aksrt-mobile-topbar__title">{{ title }}</div>
|
<div class="jio-mobile-topbar__title">{{ title }}</div>
|
||||||
|
|
||||||
<div class="aksrt-mobile-topbar__brand">AK</div>
|
<div class="jio-mobile-topbar__brand">AK</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
defineProps({
|
||||||
title: { type: String, default: 'AKSRT' },
|
title: { type: String, default: 'Jio' },
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['toggle'])
|
defineEmits(['toggle'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-mobile-topbar {
|
.jio-mobile-topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
background: var(--aksrt-color-surface);
|
background: var(--jio-color-surface);
|
||||||
border-bottom: 1px solid var(--aksrt-color-border);
|
border-bottom: 1px solid var(--jio-color-border);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-mobile-topbar__menu {
|
.jio-mobile-topbar__menu {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -50,26 +50,26 @@ defineEmits(['toggle'])
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-mobile-topbar__menu:hover {
|
.jio-mobile-topbar__menu:hover {
|
||||||
background: var(--aksrt-color-primary-soft);
|
background: var(--jio-color-primary-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-mobile-topbar__title {
|
.jio-mobile-topbar__title {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-mobile-topbar__brand {
|
.jio-mobile-topbar__brand {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -77,7 +77,7 @@ defineEmits(['toggle'])
|
|||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--aksrt-color-primary);
|
background: var(--jio-color-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="aksrt-page-header">
|
<header class="jio-page-header">
|
||||||
<div class="aksrt-page-header__main">
|
<div class="jio-page-header__main">
|
||||||
<h2 class="aksrt-page-header__title">{{ title }}</h2>
|
<h2 class="jio-page-header__title">{{ title }}</h2>
|
||||||
<p v-if="desc" class="aksrt-page-header__desc">
|
<p v-if="desc" class="jio-page-header__desc">
|
||||||
<slot name="desc">{{ desc }}</slot>
|
<slot name="desc">{{ desc }}</slot>
|
||||||
</p>
|
</p>
|
||||||
<div v-else-if="$slots.desc" class="aksrt-page-header__desc">
|
<div v-else-if="$slots.desc" class="jio-page-header__desc">
|
||||||
<slot name="desc" />
|
<slot name="desc" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$slots.actions" class="aksrt-page-header__actions">
|
<div v-if="$slots.actions" class="jio-page-header__actions">
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -23,7 +23,7 @@ defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-page-header {
|
.jio-page-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -32,28 +32,28 @@ defineProps({
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__main {
|
.jio-page-header__main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__title {
|
.jio-page-header__title {
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__desc {
|
.jio-page-header__desc {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__actions {
|
.jio-page-header__actions {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -61,20 +61,20 @@ defineProps({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.aksrt-page-header {
|
.jio-page-header {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__title {
|
.jio-page-header__title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__desc {
|
.jio-page-header__desc {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-page-header__actions {
|
.jio-page-header__actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,9 @@
|
|||||||
<n-form-item label="AccessKey Secret">
|
<n-form-item label="AccessKey Secret">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="model.access_key_secret"
|
v-model:value="model.access_key_secret"
|
||||||
type="password"
|
type="text"
|
||||||
show-password-on="click"
|
show-password-on="click"
|
||||||
|
class="jio-secret-input"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
placeholder="阿里云 RAM 用户 AccessKey Secret"
|
placeholder="阿里云 RAM 用户 AccessKey Secret"
|
||||||
@update:value="onSecretInput('access_key_secret', $event)"
|
@update:value="onSecretInput('access_key_secret', $event)"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<n-input v-model:value="model.site_key" placeholder="Cloudflare Turnstile Site Key" autocomplete="off" />
|
<n-input v-model:value="model.site_key" placeholder="Cloudflare Turnstile Site Key" autocomplete="off" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="Secret Key">
|
<n-form-item label="Secret Key">
|
||||||
<n-input v-model:value="model.secret_key" type="password" show-password-on="click" autocomplete="new-password" @update:value="onSecretInput('secret_key', $event)" />
|
<n-input v-model:value="model.secret_key" type="text" show-password-on="click" class="jio-secret-input" autocomplete="new-password" @update:value="onSecretInput('secret_key', $event)" />
|
||||||
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="主题">
|
<n-form-item label="主题">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<n-input v-model:value="model.captcha_id" placeholder="极验 GeeTest CaptchaID" autocomplete="off" />
|
<n-input v-model:value="model.captcha_id" placeholder="极验 GeeTest CaptchaID" autocomplete="off" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="Captcha Key">
|
<n-form-item label="Captcha Key">
|
||||||
<n-input v-model:value="model.captcha_key" type="password" show-password-on="click" autocomplete="new-password" @update:value="onSecretInput('captcha_key', $event)" />
|
<n-input v-model:value="model.captcha_key" type="text" show-password-on="click" class="jio-secret-input" autocomplete="new-password" @update:value="onSecretInput('captcha_key', $event)" />
|
||||||
<n-button v-if="model.captcha_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('captcha_key')">清除</n-button>
|
<n-button v-if="model.captcha_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('captcha_key')">清除</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-tag v-if="isConfigured" type="success" size="small">✓ 已完整配置</n-tag>
|
<n-tag v-if="isConfigured" type="success" size="small">✓ 已完整配置</n-tag>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<n-input v-model:value="model.secret_id" placeholder="腾讯云 API SecretId" autocomplete="off" />
|
<n-input v-model:value="model.secret_id" placeholder="腾讯云 API SecretId" autocomplete="off" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="SecretKey">
|
<n-form-item label="SecretKey">
|
||||||
<n-input v-model:value="model.secret_key" type="password" placeholder="腾讯云 API SecretKey" autocomplete="new-password" show-password-on="click" @update:value="onSecretInput('secret_key', $event)" />
|
<n-input v-model:value="model.secret_key" type="text" class="jio-secret-input" placeholder="腾讯云 API SecretKey" autocomplete="off" @update:value="onSecretInput('secret_key', $event)" />
|
||||||
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<n-input v-model:value="model.app_id" placeholder="从验证码控制台获取 CaptchaAppId" autocomplete="off" />
|
<n-input v-model:value="model.app_id" placeholder="从验证码控制台获取 CaptchaAppId" autocomplete="off" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="AppSecretKey">
|
<n-form-item label="AppSecretKey">
|
||||||
<n-input v-model:value="model.app_secret_key" type="password" placeholder="从验证码控制台获取 AppSecretKey" autocomplete="new-password" show-password-on="click" @update:value="onSecretInput('app_secret_key', $event)" />
|
<n-input v-model:value="model.app_secret_key" type="text" class="jio-secret-input" placeholder="从验证码控制台获取 AppSecretKey" autocomplete="off" @update:value="onSecretInput('app_secret_key', $event)" />
|
||||||
<n-button v-if="model.app_secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('app_secret_key')">清除</n-button>
|
<n-button v-if="model.app_secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('app_secret_key')">清除</n-button>
|
||||||
<template #feedback>请从「验证管理 → 基础配置」完整复制,注意区分 I/l/1/O/0</template>
|
<template #feedback>请从「验证管理 → 基础配置」完整复制,注意区分 I/l/1/O/0</template>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-form label-placement="left" label-width="110" class="aksrt-form float-common-fields">
|
<n-form label-placement="left" label-width="110" class="jio-form float-common-fields">
|
||||||
<n-form-item label="PC端显示" :show-feedback="false">
|
<n-form-item label="PC端显示" :show-feedback="false">
|
||||||
<n-switch :value="!!modelValue.pc_s" @update:value="update('pc_s', $event)" />
|
<n-switch :value="!!modelValue.pc_s" @update:value="update('pc_s', $event)" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="float-common-fields__colors">
|
<div class="float-common-fields__colors">
|
||||||
<div class="float-common-fields__color">
|
<div class="float-common-fields__color">
|
||||||
<span>自定义图标颜色</span>
|
<span>自定义图标颜色</span>
|
||||||
<AksColorPicker
|
<JioColorPicker
|
||||||
:model-value="modelValue.color?.color || ''"
|
:model-value="modelValue.color?.color || ''"
|
||||||
alpha
|
alpha
|
||||||
clearable
|
clearable
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="float-common-fields__color">
|
<div class="float-common-fields__color">
|
||||||
<span>自定义背景颜色</span>
|
<span>自定义背景颜色</span>
|
||||||
<AksColorPicker
|
<JioColorPicker
|
||||||
:model-value="modelValue.color?.bg || ''"
|
:model-value="modelValue.color?.bg || ''"
|
||||||
alpha
|
alpha
|
||||||
clearable
|
clearable
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import AksColorPicker from '@/components/form/AksColorPicker.vue'
|
import JioColorPicker from '@/components/form/JioColorPicker.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: Object, required: true },
|
modelValue: { type: Object, required: true },
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="float-btn-panel">
|
<div class="float-btn-panel">
|
||||||
<div class="aksrt-sortable__head" v-bind="headAttrs">
|
<div class="jio-sortable__head" v-bind="headAttrs">
|
||||||
<AksSortHandle @click.stop />
|
<JioSortHandle @click.stop />
|
||||||
<div class="aksrt-sortable__main">
|
<div class="jio-sortable__main">
|
||||||
<div class="aksrt-sortable__title-row">
|
<div class="jio-sortable__title-row">
|
||||||
<span class="aksrt-sortable__title">{{ meta.title }}</span>
|
<span class="jio-sortable__title">{{ meta.title }}</span>
|
||||||
<template v-if="modelValue.type !== 'more'">
|
<template v-if="modelValue.type !== 'more'">
|
||||||
<n-tag v-if="modelValue.pc_s" size="small" type="info" :bordered="false">PC</n-tag>
|
<n-tag v-if="modelValue.pc_s" size="small" type="info" :bordered="false">PC</n-tag>
|
||||||
<n-tag v-if="modelValue.m_s" size="small" type="warning" :bordered="false">移动</n-tag>
|
<n-tag v-if="modelValue.m_s" size="small" type="warning" :bordered="false">移动</n-tag>
|
||||||
@@ -12,18 +12,18 @@
|
|||||||
</template>
|
</template>
|
||||||
<n-tag v-else size="small" :bordered="false">{{ modelValue.buttons.length }} 个</n-tag>
|
<n-tag v-else size="small" :bordered="false">{{ modelValue.buttons.length }} 个</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="meta.warn" class="aksrt-sortable__desc float-btn-panel__warn">
|
<span v-if="meta.warn" class="jio-sortable__desc float-btn-panel__warn">
|
||||||
<i class="fa fas fa-info-circle" aria-hidden="true" /> {{ meta.warn }}
|
<i class="fa fas fa-info-circle" aria-hidden="true" /> {{ meta.warn }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
class="fa fas aksrt-sortable__toggle"
|
class="fa fas jio-sortable__toggle"
|
||||||
:class="expanded ? 'fa-chevron-down' : 'fa-chevron-right'"
|
:class="expanded ? 'fa-chevron-down' : 'fa-chevron-right'"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="expanded" class="aksrt-sortable__body">
|
<div v-show="expanded" class="jio-sortable__body">
|
||||||
<div class="float-btn-panel__subtitle">{{ meta.panel }}</div>
|
<div class="float-btn-panel__subtitle">{{ meta.panel }}</div>
|
||||||
|
|
||||||
<FloatMoreButtonsEditor
|
<FloatMoreButtonsEditor
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<FloatBtnCommonFields :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" />
|
<FloatBtnCommonFields :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" />
|
||||||
|
|
||||||
<n-form label-placement="left" label-width="110" class="aksrt-form float-btn-panel__extra">
|
<n-form label-placement="left" label-width="110" class="jio-form float-btn-panel__extra">
|
||||||
<n-form-item v-if="modelValue.type === 'qrcode'" label="一句话简介">
|
<n-form-item v-if="modelValue.type === 'qrcode'" label="一句话简介">
|
||||||
<n-input :value="modelValue.desc" @update:value="patch({ desc: $event })" />
|
<n-input :value="modelValue.desc" @update:value="patch({ desc: $event })" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item v-if="modelValue.type === 'service_wechat'" label="微信二维码">
|
<n-form-item v-if="modelValue.type === 'service_wechat'" label="微信二维码">
|
||||||
<AksMediaField
|
<JioMediaField
|
||||||
:model-value="modelValue.wechat_img"
|
:model-value="modelValue.wechat_img"
|
||||||
placeholder="选择二维码图片"
|
placeholder="选择二维码图片"
|
||||||
@update:model-value="patch({ wechat_img: $event })"
|
@update:model-value="patch({ wechat_img: $event })"
|
||||||
@@ -62,11 +62,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/** 悬浮按钮排序列表中的单个卡片;外层 .aksrt-sortable__item 由 FloatingButtonsSettings 提供。 */
|
/** 悬浮按钮排序列表中的单个卡片;外层 .jio-sortable__item 由 FloatingButtonsSettings 提供。 */
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||||
import AksMediaField from '@/components/form/AksMediaField.vue'
|
import JioMediaField from '@/components/form/JioMediaField.vue'
|
||||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||||
import { collapseHeadAttrs } from '@/components/form/useCollapsibleItems.js'
|
import { collapseHeadAttrs } from '@/components/form/useCollapsibleItems.js'
|
||||||
import FloatBtnCommonFields from './FloatBtnCommonFields.vue'
|
import FloatBtnCommonFields from './FloatBtnCommonFields.vue'
|
||||||
import FloatMoreButtonsEditor from './FloatMoreButtonsEditor.vue'
|
import FloatMoreButtonsEditor from './FloatMoreButtonsEditor.vue'
|
||||||
@@ -93,12 +93,12 @@ function patch(partial) {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.float-btn-panel__warn {
|
.float-btn-panel__warn {
|
||||||
color: var(--aksrt-color-warning, #D97706);
|
color: var(--jio-color-warning, #D97706);
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-btn-panel__subtitle {
|
.float-btn-panel__subtitle {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<n-form label-placement="left" label-width="110" class="aksrt-form">
|
<n-form label-placement="left" label-width="110" class="jio-form">
|
||||||
<n-form-item label="按钮简介">
|
<n-form-item label="按钮简介">
|
||||||
<n-input :value="btn.desc" @update:value="patch(index, { desc: $event })" />
|
<n-input :value="btn.desc" @update:value="patch(index, { desc: $event })" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|||||||
@@ -16,32 +16,21 @@ export const FLOAT_BTN_META = {
|
|||||||
more: { title: '更多按钮', panel: '添加更多自定义按钮' },
|
more: { title: '更多按钮', panel: '添加更多自定义按钮' },
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeUri = (window.aksrtAdminData?.themeUri || '').replace(/\/$/, '')
|
/** 「更多按钮」出厂默认项:不预设任何第三方指向,只给一个图标按钮。 */
|
||||||
|
|
||||||
/** 「更多按钮」出厂默认项(对齐参考主题 float_btn.more 默认值)。 */
|
|
||||||
export function giftMoreButton() {
|
export function giftMoreButton() {
|
||||||
return {
|
return {
|
||||||
desc: '本站同款主题模板',
|
desc: '',
|
||||||
icon: 'zibsvg-gift-color',
|
icon: 'jiosvg-gift-color',
|
||||||
link: { url: '', target: '' },
|
link: { url: '', target: '' },
|
||||||
hover: `<a href="https://www.zibll.com/" target="_blank">
|
hover: '',
|
||||||
<div class="flex c-red">
|
hover_width: 200,
|
||||||
<img class="flex0" alt="zibll子比主题" src="${themeUri}/assets/img/logo.png" height="30">
|
|
||||||
<div class="flex1 ml10">
|
|
||||||
<dt>本站同款主题模板</dt>
|
|
||||||
<div class="px12 mt10 muted-color">zibll子比主题是一款漂亮优雅的网站主题模板,功能强大,配置简单。</div>
|
|
||||||
<div class="but mt10 p2-10 c-blue btn-block px12">查看详情</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>`,
|
|
||||||
hover_width: 240,
|
|
||||||
pc_s: true,
|
pc_s: true,
|
||||||
m_s: true,
|
m_s: true,
|
||||||
color: { color: '', bg: 'rgba(255, 111, 6, 0.2)' },
|
color: { color: '', bg: 'rgba(255, 111, 6, 0.2)' },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 「添加按钮」新增项默认值(参考主题 group 字段默认值)。 */
|
/** 「添加按钮」新增项默认值。 */
|
||||||
export function defaultMoreButton() {
|
export function defaultMoreButton() {
|
||||||
return {
|
return {
|
||||||
desc: '',
|
desc: '',
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="aksrt-image-select" role="radiogroup">
|
|
||||||
<button
|
|
||||||
v-for="opt in options"
|
|
||||||
:key="opt.value"
|
|
||||||
type="button"
|
|
||||||
role="radio"
|
|
||||||
class="aksrt-image-select__item"
|
|
||||||
:class="{ 'is-active': modelValue === opt.value }"
|
|
||||||
:aria-checked="modelValue === opt.value"
|
|
||||||
:disabled="disabled"
|
|
||||||
@click="emit('update:modelValue', opt.value)"
|
|
||||||
>
|
|
||||||
<img v-if="opt.image" :src="opt.image" :alt="opt.label" class="aksrt-image-select__img" />
|
|
||||||
<span v-else class="aksrt-image-select__placeholder">
|
|
||||||
<FaIcon :icon="opt.icon || 'fa fas fa-image'" :size="20" />
|
|
||||||
</span>
|
|
||||||
<span class="aksrt-image-select__label">{{ opt.label }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
/** image_select:带预览图的单选。 */
|
|
||||||
import FaIcon from './FaIcon.vue'
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
modelValue: { default: null },
|
|
||||||
options: { type: Array, default: () => [] },
|
|
||||||
disabled: { type: Boolean, default: false },
|
|
||||||
})
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.aksrt-image-select {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
min-width: 80px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 2px solid var(--aksrt-color-border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--aksrt-color-surface);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color .15s ease, background-color .15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__item:hover,
|
|
||||||
.aksrt-image-select__item:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--aksrt-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__item.is-active {
|
|
||||||
border-color: var(--aksrt-color-primary);
|
|
||||||
background: var(--aksrt-color-primary-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__item:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__img {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__placeholder {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--aksrt-color-canvas);
|
|
||||||
color: var(--aksrt-color-border-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-image-select__label {
|
|
||||||
color: var(--aksrt-color-text-secondary);
|
|
||||||
font-size: 11px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.aksrt-image-select__item {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-fa-icon-picker">
|
<div class="jio-fa-icon-picker">
|
||||||
<n-popover v-model:show="popoverShow" trigger="click" placement="bottom-start" :width="460" :style="{ padding: 0 }">
|
<n-popover v-model:show="popoverShow" trigger="click" placement="bottom-start" :width="460" :style="{ padding: 0 }">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-input
|
<n-input
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
</n-input>
|
</n-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="aksrt-icon-picker__panel" @click="onPanelClick">
|
<div class="jio-icon-picker__panel" @click="onPanelClick">
|
||||||
<div class="aksrt-icon-picker__search">
|
<div class="jio-icon-picker__search">
|
||||||
<n-input v-model:value="search" placeholder="搜索图标..." size="small" clearable>
|
<n-input v-model:value="search" placeholder="搜索图标..." size="small" clearable>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<FaIcon icon="fa fas fa-search" :size="12" />
|
<FaIcon icon="fa fas fa-search" :size="12" />
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
</n-input>
|
</n-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="aksrt-icon-picker__tabs">
|
<div class="jio-icon-picker__tabs">
|
||||||
<button
|
<button
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
type="button"
|
type="button"
|
||||||
class="aksrt-icon-picker__tab"
|
class="jio-icon-picker__tab"
|
||||||
:class="{ 'is-active': activeTab === tab.id }"
|
:class="{ 'is-active': activeTab === tab.id }"
|
||||||
@click="activeTab = tab.id"
|
@click="activeTab = tab.id"
|
||||||
>
|
>
|
||||||
@@ -42,23 +42,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Custom SVG tab -->
|
<!-- Custom SVG tab -->
|
||||||
<div v-if="activeTab === 'custom'" class="aksrt-icon-picker__custom">
|
<div v-if="activeTab === 'custom'" class="jio-icon-picker__custom">
|
||||||
<n-input v-model:value="customCode" type="textarea" placeholder='<svg viewBox="0 0 1024 1024"><path d="…"></path></svg>' :rows="4" />
|
<n-input v-model:value="customCode" type="textarea" placeholder='<svg viewBox="0 0 1024 1024"><path d="…"></path></svg>' :rows="4" />
|
||||||
<div class="aksrt-icon-picker__custom-actions">
|
<div class="jio-icon-picker__custom-actions">
|
||||||
<span class="aksrt-icon-picker__custom-preview" v-html="customPreview"></span>
|
<span class="jio-icon-picker__custom-preview" v-html="customPreview"></span>
|
||||||
<n-button size="small" type="primary" :disabled="!customPreview" @click="insertCustom">
|
<n-button size="small" type="primary" :disabled="!customPreview" @click="insertCustom">
|
||||||
确认插入
|
确认插入
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="sessionCustom.length"
|
v-if="sessionCustom.length"
|
||||||
class="aksrt-icon-picker__grid aksrt-icon-picker__grid--session"
|
class="jio-icon-picker__grid jio-icon-picker__grid--session"
|
||||||
data-aksrt-icon-grid="custom"
|
data-jio-icon-grid="custom"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(svg, i) in sessionCustom"
|
v-for="(svg, i) in sessionCustom"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="aksrt-icon-picker__item"
|
class="jio-icon-picker__item"
|
||||||
:class="{ 'is-active': modelValue === `customsvg-${svg}` }"
|
:class="{ 'is-active': modelValue === `customsvg-${svg}` }"
|
||||||
title="customsvg"
|
title="customsvg"
|
||||||
:data-icon="`customsvg-${svg}`"
|
:data-icon="`customsvg-${svg}`"
|
||||||
@@ -68,11 +68,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Catalog tab -->
|
<!-- Catalog tab -->
|
||||||
<div v-else class="aksrt-icon-picker__grid" data-aksrt-icon-grid="catalog">
|
<div v-else class="jio-icon-picker__grid" data-jio-icon-grid="catalog">
|
||||||
<div
|
<div
|
||||||
v-for="icon in filteredIcons"
|
v-for="icon in filteredIcons"
|
||||||
:key="icon"
|
:key="icon"
|
||||||
class="aksrt-icon-picker__item"
|
class="jio-icon-picker__item"
|
||||||
:class="{ 'is-active': modelValue === composeIconValue(activeGroup, icon) }"
|
:class="{ 'is-active': modelValue === composeIconValue(activeGroup, icon) }"
|
||||||
:title="icon"
|
:title="icon"
|
||||||
:data-icon="composeIconValue(activeGroup, icon)"
|
:data-icon="composeIconValue(activeGroup, icon)"
|
||||||
@@ -80,11 +80,11 @@
|
|||||||
<i v-if="activeGroup.type === 'fa'" :class="[activeTab, 'fa-' + icon]"></i>
|
<i v-if="activeGroup.type === 'fa'" :class="[activeTab, 'fa-' + icon]"></i>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
class="aksrt-icon-picker__preview-svg"
|
class="jio-icon-picker__preview-svg"
|
||||||
v-html="zibsvgMarkup(`zibsvg-${icon}`)"
|
v-html="jiosvgMarkup(`jiosvg-${icon}`)"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="filteredIcons.length === 0" class="aksrt-icon-picker__empty">
|
<div v-if="filteredIcons.length === 0" class="jio-icon-picker__empty">
|
||||||
未找到匹配图标
|
未找到匹配图标
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* FaIconPicker — universal icon picker backed by the server catalog
|
* FaIconPicker — universal icon picker backed by the server catalog
|
||||||
* (GET /aksrt/v1/icons/catalog): full FA5 free set, the theme's built-in
|
* (GET /jio/v1/icons/catalog): full FA5 free set, the theme's built-in
|
||||||
* SVG sprite keys and inline custom SVG values.
|
* SVG sprite keys and inline custom SVG values.
|
||||||
*/
|
*/
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
@@ -105,7 +105,7 @@ import IconValue from './IconValue.vue'
|
|||||||
import api from '@/api/index.js'
|
import api from '@/api/index.js'
|
||||||
import {
|
import {
|
||||||
cleanSvg,
|
cleanSvg,
|
||||||
zibsvgMarkup,
|
jiosvgMarkup,
|
||||||
composeIconValue,
|
composeIconValue,
|
||||||
} from '@/utils/icons.js'
|
} from '@/utils/icons.js'
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ const FALLBACK_TABS = [
|
|||||||
{ id: 'fas', title: 'Solid', type: 'fa', icons: [] },
|
{ id: 'fas', title: 'Solid', type: 'fa', icons: [] },
|
||||||
{ id: 'far', title: 'Regular', type: 'fa', icons: [] },
|
{ id: 'far', title: 'Regular', type: 'fa', icons: [] },
|
||||||
{ id: 'fab', title: 'Brands', type: 'fa', icons: [] },
|
{ id: 'fab', title: 'Brands', type: 'fa', icons: [] },
|
||||||
{ id: 'zibsvg', title: '内置 SVG', type: 'svg', icons: [] },
|
{ id: 'jiosvg', title: '内置 SVG', type: 'svg', icons: [] },
|
||||||
{ id: 'custom', title: '自定义', type: 'custom', icons: [] },
|
{ id: 'custom', title: '自定义', type: 'custom', icons: [] },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -176,53 +176,53 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-icon-picker__panel {
|
.jio-icon-picker__panel {
|
||||||
/* The popover teleports to <body>, outside #aksrt-admin-app where the
|
/* The popover teleports to <body>, outside #jio-admin-app where the
|
||||||
palette is declared — re-declare every token the panel consumes. */
|
palette is declared — re-declare every token the panel consumes. */
|
||||||
--aksrt-color-primary: #18A058;
|
--jio-color-primary: #18A058;
|
||||||
--aksrt-color-primary-hover: #36AD6A;
|
--jio-color-primary-hover: #36AD6A;
|
||||||
--aksrt-color-primary-soft: rgba(24, 160, 88, 0.12);
|
--jio-color-primary-soft: rgba(24, 160, 88, 0.12);
|
||||||
--aksrt-color-text-secondary: #465149;
|
--jio-color-text-secondary: #465149;
|
||||||
--aksrt-color-muted: #6F7A72;
|
--jio-color-muted: #6F7A72;
|
||||||
--aksrt-color-border: #E2E8E3;
|
--jio-color-border: #E2E8E3;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__search {
|
.jio-icon-picker__search {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__tabs {
|
.jio-icon-picker__tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__tab {
|
.jio-icon-picker__tab {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__tab:hover {
|
.jio-icon-picker__tab:hover {
|
||||||
border-color: var(--aksrt-color-primary);
|
border-color: var(--jio-color-primary);
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__tab.is-active {
|
.jio-icon-picker__tab.is-active {
|
||||||
border-color: var(--aksrt-color-primary);
|
border-color: var(--jio-color-primary);
|
||||||
background: var(--aksrt-color-primary);
|
background: var(--jio-color-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__grid {
|
.jio-icon-picker__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(8, 1fr);
|
grid-template-columns: repeat(8, 1fr);
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -230,7 +230,7 @@ onMounted(async () => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__item {
|
.jio-icon-picker__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -239,60 +239,60 @@ onMounted(async () => {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__item:hover {
|
.jio-icon-picker__item:hover {
|
||||||
background: var(--aksrt-color-primary-soft);
|
background: var(--jio-color-primary-soft);
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__item.is-active {
|
.jio-icon-picker__item.is-active {
|
||||||
background: var(--aksrt-color-primary);
|
background: var(--jio-color-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__item :deep(svg),
|
.jio-icon-picker__item :deep(svg),
|
||||||
.aksrt-icon-picker__preview-svg :deep(svg),
|
.jio-icon-picker__preview-svg :deep(svg),
|
||||||
.aksrt-icon-picker__preview-svg svg {
|
.jio-icon-picker__preview-svg svg {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__preview-svg {
|
.jio-icon-picker__preview-svg {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__empty {
|
.jio-icon-picker__empty {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__custom-actions {
|
.jio-icon-picker__custom-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__custom-preview {
|
.jio-icon-picker__custom-preview {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 1px dashed var(--aksrt-color-muted);
|
border: 1px dashed var(--jio-color-muted);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-picker__grid--session {
|
.jio-icon-picker__grid--session {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
border-top: 1px solid var(--aksrt-color-muted);
|
border-top: 1px solid var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,25 +3,25 @@
|
|||||||
<i v-if="isFaValue(value)" :class="faClass" aria-hidden="true"></i>
|
<i v-if="isFaValue(value)" :class="faClass" aria-hidden="true"></i>
|
||||||
<!-- Built-in sprite value -->
|
<!-- Built-in sprite value -->
|
||||||
<span
|
<span
|
||||||
v-else-if="isZibsvgValue(value)"
|
v-else-if="isJiosvgValue(value)"
|
||||||
class="aksrt-icon-value__svg"
|
class="jio-icon-value__svg"
|
||||||
v-html="zibsvgMarkup(value)"
|
v-html="jiosvgMarkup(value)"
|
||||||
></span>
|
></span>
|
||||||
<!-- Inline custom SVG value -->
|
<!-- Inline custom SVG value -->
|
||||||
<span
|
<span
|
||||||
v-else-if="isCustomSvgValue(value)"
|
v-else-if="isCustomSvgValue(value)"
|
||||||
class="aksrt-icon-value__svg"
|
class="jio-icon-value__svg"
|
||||||
v-html="cleanSvg(value.slice(10))"
|
v-html="cleanSvg(value.slice(10))"
|
||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* IconValue — renders any icon-picker value ("fa fas fa-star", "zibsvg-key",
|
* IconValue — renders any icon-picker value ("fa fas fa-star", "jiosvg-key",
|
||||||
* "customsvg-<svg>") as inline preview markup.
|
* "customsvg-<svg>") as inline preview markup.
|
||||||
*/
|
*/
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { cleanSvg, isFaValue, isZibsvgValue, isCustomSvgValue, zibsvgMarkup } from '@/utils/icons.js'
|
import { cleanSvg, isFaValue, isJiosvgValue, isCustomSvgValue, jiosvgMarkup } from '@/utils/icons.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: { type: String, default: '' },
|
value: { type: String, default: '' },
|
||||||
@@ -35,12 +35,12 @@ const faClass = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-icon-value__svg {
|
.jio-icon-value__svg {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-icon-value__svg :deep(svg),
|
.jio-icon-value__svg :deep(svg),
|
||||||
.aksrt-icon-value__svg svg {
|
.jio-icon-value__svg svg {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
|
|||||||
+5
-5
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="editorRef" class="aks-code-editor" :class="{ 'is-disabled': disabled }" />
|
<div ref="editorRef" class="jio-code-editor" :class="{ 'is-disabled': disabled }" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -86,19 +86,19 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aks-code-editor {
|
.jio-code-editor {
|
||||||
border: 1px solid var(--n-border-color, #e0e0e6);
|
border: 1px solid var(--n-border-color, #e0e0e6);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: calc(var(--rows, 8) * 22px);
|
min-height: calc(var(--rows, 8) * 22px);
|
||||||
}
|
}
|
||||||
.aks-code-editor :deep(.cm-editor) {
|
.jio-code-editor :deep(.cm-editor) {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.aks-code-editor :deep(.cm-editor.cm-focused) {
|
.jio-code-editor :deep(.cm-editor.cm-focused) {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.aks-code-editor.is-disabled {
|
.jio-code-editor.is-disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* 后台唯一取色器:所有颜色字段都经由此组件,输出与
|
* 后台唯一取色器:所有颜色字段都经由此组件,输出与
|
||||||
* PHP `AKSRT\Core\Color::sanitize($value, $allow_alpha)` 同一规则的规范值。
|
* PHP `Jio\Core\Color::sanitize($value, $allow_alpha)` 同一规则的规范值。
|
||||||
* 清空 / 非法值统一回传空串。
|
* 清空 / 非法值统一回传空串。
|
||||||
*/
|
*/
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
+9
-9
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Between number (min/max pair) -->
|
<!-- Between number (min/max pair) -->
|
||||||
<div class="aksrt-composite aksrt-composite--between">
|
<div class="jio-composite jio-composite--between">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
:value="betweenValue('min')"
|
:value="betweenValue('min')"
|
||||||
:min="schema.min"
|
:min="schema.min"
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
style="flex: 1"
|
style="flex: 1"
|
||||||
@update:value="update('min', $event)"
|
@update:value="update('min', $event)"
|
||||||
/>
|
/>
|
||||||
<span class="aksrt-composite__sep">—</span>
|
<span class="jio-composite__sep">—</span>
|
||||||
<n-input-number
|
<n-input-number
|
||||||
:value="betweenValue('max')"
|
:value="betweenValue('max')"
|
||||||
:min="schema.min"
|
:min="schema.min"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
style="flex: 1"
|
style="flex: 1"
|
||||||
@update:value="update('max', $event)"
|
@update:value="update('max', $event)"
|
||||||
/>
|
/>
|
||||||
<span v-if="schema.unit" class="aksrt-composite__unit">{{ schema.unit }}</span>
|
<span v-if="schema.unit" class="jio-composite__unit">{{ schema.unit }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -52,23 +52,23 @@ function betweenValue(part) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-composite {
|
.jio-composite {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-composite--between {
|
.jio-composite--between {
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-composite__sep {
|
.jio-composite__sep {
|
||||||
color: var(--aksrt-color-border-strong);
|
color: var(--jio-color-border-strong);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-composite__unit {
|
.jio-composite__unit {
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
+161
-138
@@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-field" :class="fieldClasses">
|
<div class="jio-field" :class="fieldClasses">
|
||||||
<div v-if="schema.label" class="aksrt-field__label-row">
|
<div v-if="schema.label" class="jio-field__label-row">
|
||||||
<label class="aksrt-field__label">
|
<label class="jio-field__label">
|
||||||
{{ schema.label }}
|
{{ schema.label }}
|
||||||
<span v-if="schema.required" class="aksrt-field__required">*</span>
|
<span v-if="schema.required" class="jio-field__required">*</span>
|
||||||
</label>
|
</label>
|
||||||
<n-tooltip v-if="schema.help" trigger="hover">
|
<n-tooltip v-if="schema.help" trigger="hover">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<FaIcon icon="fa fas fa-question-circle" :size="13" class="aksrt-field__help-icon" />
|
<FaIcon icon="fa fas fa-question-circle" :size="13" class="jio-field__help-icon" />
|
||||||
</template>
|
</template>
|
||||||
{{ schema.help }}
|
{{ schema.help }}
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="schema.description && !schema.help" class="aksrt-field__desc">
|
<p v-if="schema.description && !schema.help" class="jio-field__desc">
|
||||||
{{ schema.description }}
|
{{ schema.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -25,22 +25,25 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Input (text, email, url, password) -->
|
<!-- Input (text, email, url, password) -->
|
||||||
<div v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'" class="aksrt-field__input-wrap">
|
<div v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'" class="jio-field__input-wrap">
|
||||||
<n-input
|
<n-input
|
||||||
:value="secretDisplayValue"
|
:value="secretDisplayValue"
|
||||||
:placeholder="schema.placeholder || ''"
|
:placeholder="schema.placeholder || ''"
|
||||||
:type="schema.inputType || 'text'"
|
:type="schema.secret ? 'text' : (schema.inputType || 'text')"
|
||||||
|
:class="{ 'jio-secret-input': schema.secret }"
|
||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
|
:name="schema.secret ? 'jio-secret-' + schema.key : schema.key"
|
||||||
|
:input-props="schema.secret ? secretInputProps : undefined"
|
||||||
@update:value="emit('update:modelValue', $event)"
|
@update:value="emit('update:modelValue', $event)"
|
||||||
>
|
>
|
||||||
<template v-if="schema.prefix" #prefix>{{ schema.prefix }}</template>
|
<template v-if="schema.prefix" #prefix>{{ schema.prefix }}</template>
|
||||||
<template v-if="schema.suffix" #suffix>{{ schema.suffix }}</template>
|
<template v-if="schema.suffix" #suffix>{{ schema.suffix }}</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
<AksSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
<JioSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Textarea(secret 时不回显已配置值,聚焦后输入新值即覆盖) -->
|
<!-- Textarea(secret 时不回显已配置值,聚焦后输入新值即覆盖) -->
|
||||||
<div v-else-if="schema.type === 'textarea'" class="aksrt-field__input-wrap">
|
<div v-else-if="schema.type === 'textarea'" class="jio-field__input-wrap">
|
||||||
<n-input
|
<n-input
|
||||||
:value="secretTextareaValue"
|
:value="secretTextareaValue"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -49,11 +52,11 @@
|
|||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
@update:value="emit('update:modelValue', $event)"
|
@update:value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
<AksSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
<JioSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Number -->
|
<!-- Number -->
|
||||||
<div v-else-if="schema.type === 'number' || schema.type === 'spinner'" class="aksrt-field__number">
|
<div v-else-if="schema.type === 'number' || schema.type === 'spinner'" class="jio-field__number">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
:min="schema.min"
|
:min="schema.min"
|
||||||
@@ -64,11 +67,11 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@update:value="emit('update:modelValue', $event)"
|
@update:value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
<span v-if="schema.unit" class="aksrt-field__unit">{{ schema.unit }}</span>
|
<span v-if="schema.unit" class="jio-field__unit">{{ schema.unit }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Select -->
|
<!-- Select -->
|
||||||
<div v-else-if="schema.type === 'select'" class="aksrt-field__ordered-select">
|
<div v-else-if="schema.type === 'select'" class="jio-field__ordered-select">
|
||||||
<n-select
|
<n-select
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
:options="normalizedOptions"
|
:options="normalizedOptions"
|
||||||
@@ -78,7 +81,7 @@
|
|||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
@update:value="emit('update:modelValue', $event)"
|
@update:value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
<AksSelectedOrder
|
<JioSelectedOrder
|
||||||
v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)"
|
v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:options="normalizedOptions"
|
:options="normalizedOptions"
|
||||||
@@ -87,7 +90,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="schema.type === 'async_select'" class="aksrt-field__ordered-select">
|
<div v-else-if="schema.type === 'async_select'" class="jio-field__ordered-select">
|
||||||
<n-select
|
<n-select
|
||||||
:value="schema.multiple === true && !Array.isArray(modelValue) ? [] : modelValue"
|
:value="schema.multiple === true && !Array.isArray(modelValue) ? [] : modelValue"
|
||||||
:options="asyncOptions"
|
:options="asyncOptions"
|
||||||
@@ -100,7 +103,7 @@
|
|||||||
@search="handleAsyncSearch"
|
@search="handleAsyncSearch"
|
||||||
@update:value="emit('update:modelValue', $event)"
|
@update:value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
<AksSelectedOrder
|
<JioSelectedOrder
|
||||||
v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)"
|
v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:options="asyncOptions"
|
:options="asyncOptions"
|
||||||
@@ -199,7 +202,7 @@
|
|||||||
</n-checkbox-group>
|
</n-checkbox-group>
|
||||||
|
|
||||||
<!-- Color picker -->
|
<!-- Color picker -->
|
||||||
<AksColorPicker
|
<JioColorPicker
|
||||||
v-else-if="schema.type === 'color'"
|
v-else-if="schema.type === 'color'"
|
||||||
:model-value="typeof modelValue === 'string' ? modelValue : ''"
|
:model-value="typeof modelValue === 'string' ? modelValue : ''"
|
||||||
:alpha="!!schema.showAlpha"
|
:alpha="!!schema.showAlpha"
|
||||||
@@ -208,12 +211,12 @@
|
|||||||
@update:model-value="emit('update:modelValue', $event)"
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-else-if="schema.type === 'palette'" class="aksrt-field__palette">
|
<div v-else-if="schema.type === 'palette'" class="jio-field__palette">
|
||||||
<button
|
<button
|
||||||
v-for="opt in normalizedOptions"
|
v-for="opt in normalizedOptions"
|
||||||
:key="opt.value"
|
:key="opt.value"
|
||||||
type="button"
|
type="button"
|
||||||
class="aksrt-field__palette-item"
|
class="jio-field__palette-item"
|
||||||
:class="{ 'is-active': modelValue === opt.value }"
|
:class="{ 'is-active': modelValue === opt.value }"
|
||||||
:style="{ background: opt.label }"
|
:style="{ background: opt.label }"
|
||||||
:title="opt.value"
|
:title="opt.value"
|
||||||
@@ -230,7 +233,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Media upload -->
|
<!-- Media upload -->
|
||||||
<AksMediaField
|
<JioMediaField
|
||||||
v-else-if="schema.type === 'media' || schema.type === 'upload'"
|
v-else-if="schema.type === 'media' || schema.type === 'upload'"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:accept="schema.accept"
|
:accept="schema.accept"
|
||||||
@@ -239,7 +242,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- WordPress image gallery (attachment ID array) -->
|
<!-- WordPress image gallery (attachment ID array) -->
|
||||||
<AksGalleryField
|
<JioGalleryField
|
||||||
v-else-if="schema.type === 'gallery'"
|
v-else-if="schema.type === 'gallery'"
|
||||||
:model-value="Array.isArray(modelValue) ? modelValue : []"
|
:model-value="Array.isArray(modelValue) ? modelValue : []"
|
||||||
:title="schema.label || '选择图片'"
|
:title="schema.label || '选择图片'"
|
||||||
@@ -259,7 +262,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Code editor with syntax highlighting (CodeMirror 6) -->
|
<!-- Code editor with syntax highlighting (CodeMirror 6) -->
|
||||||
<AksCodeEditor
|
<JioCodeEditor
|
||||||
v-else-if="schema.type === 'code'"
|
v-else-if="schema.type === 'code'"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:language="schema.language || 'html'"
|
:language="schema.language || 'html'"
|
||||||
@@ -281,7 +284,7 @@
|
|||||||
<!-- Pure HTML content (no form field, just display) -->
|
<!-- Pure HTML content (no form field, just display) -->
|
||||||
<div
|
<div
|
||||||
v-else-if="schema.type === 'content'"
|
v-else-if="schema.type === 'content'"
|
||||||
class="aksrt-field__content"
|
class="jio-field__content"
|
||||||
v-html="schema.content || modelValue || ''"
|
v-html="schema.content || modelValue || ''"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -316,7 +319,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Object composite: between_number (min/max pair) -->
|
<!-- Object composite: between_number (min/max pair) -->
|
||||||
<AksCompositeField
|
<JioCompositeField
|
||||||
v-else-if="schema.type === 'between_number'"
|
v-else-if="schema.type === 'between_number'"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@@ -324,33 +327,34 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Image select (radio with image preview) -->
|
<!-- Image select (radio with image preview) -->
|
||||||
<AksImageSelectField
|
<JioImageSelectField
|
||||||
v-else-if="schema.type === 'image_select'"
|
v-else-if="schema.type === 'image_select'"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:options="normalizedOptions"
|
:options="normalizedOptions"
|
||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
|
:preview-width="schema.previewWidth"
|
||||||
@update:model-value="emit('update:modelValue', $event)"
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Fieldset (single nested object) -->
|
<!-- Fieldset (single nested object) -->
|
||||||
<div v-else-if="schema.type === 'fieldset' || schema.type === 'accordion'" class="aksrt-field__fieldset">
|
<div v-else-if="schema.type === 'fieldset' || schema.type === 'accordion'" class="jio-field__fieldset">
|
||||||
<template v-if="fieldsetSections.length">
|
<template v-if="fieldsetSections.length">
|
||||||
<div
|
<div
|
||||||
v-for="(section, sIndex) in fieldsetSections"
|
v-for="(section, sIndex) in fieldsetSections"
|
||||||
:key="section.title"
|
:key="section.title"
|
||||||
class="aksrt-field__accordion-section"
|
class="jio-field__accordion-section"
|
||||||
:class="{ 'is-collapsed': !isFieldsetSectionOpen(sIndex) }"
|
:class="{ 'is-collapsed': !isFieldsetSectionOpen(sIndex) }"
|
||||||
>
|
>
|
||||||
<div class="aksrt-field__accordion-section-head" @click="toggleFieldsetSection(sIndex)">
|
<div class="jio-field__accordion-section-head" @click="toggleFieldsetSection(sIndex)">
|
||||||
<span class="aksrt-field__accordion-section-title">{{ section.title }}</span>
|
<span class="jio-field__accordion-section-title">{{ section.title }}</span>
|
||||||
<FaIcon
|
<FaIcon
|
||||||
:icon="isFieldsetSectionOpen(sIndex) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
:icon="isFieldsetSectionOpen(sIndex) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||||
:size="12"
|
:size="12"
|
||||||
class="aksrt-field__accordion-section-toggle"
|
class="jio-field__accordion-section-toggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="isFieldsetSectionOpen(sIndex)" class="aksrt-field__accordion-section-body">
|
<div v-show="isFieldsetSectionOpen(sIndex)" class="jio-field__accordion-section-body">
|
||||||
<AksField
|
<JioField
|
||||||
v-for="field in sectionFields(section)"
|
v-for="field in sectionFields(section)"
|
||||||
:key="field.key"
|
:key="field.key"
|
||||||
:schema="field"
|
:schema="field"
|
||||||
@@ -362,7 +366,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!fieldsetSections.length">
|
<template v-if="!fieldsetSections.length">
|
||||||
<AksField
|
<JioField
|
||||||
v-for="field in visibleNestedFields"
|
v-for="field in visibleNestedFields"
|
||||||
:key="field.key"
|
:key="field.key"
|
||||||
:schema="field"
|
:schema="field"
|
||||||
@@ -373,7 +377,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="schema.type === 'link'" class="aksrt-field__link">
|
<div v-else-if="schema.type === 'link'" class="jio-field__link">
|
||||||
<n-input :value="modelValue?.url || ''" placeholder="https://" @update:value="updateLink('url', $event)" />
|
<n-input :value="modelValue?.url || ''" placeholder="https://" @update:value="updateLink('url', $event)" />
|
||||||
<n-select :value="modelValue?.target || ''" :options="linkTargetOptions" @update:value="updateLink('target', $event)" />
|
<n-select :value="modelValue?.target || ''" :options="linkTargetOptions" @update:value="updateLink('target', $event)" />
|
||||||
<n-input :value="modelValue?.text || ''" placeholder="链接文字(可选)" @update:value="updateLink('text', $event)" />
|
<n-input :value="modelValue?.text || ''" placeholder="链接文字(可选)" @update:value="updateLink('text', $event)" />
|
||||||
@@ -385,54 +389,54 @@
|
|||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
item-key="key"
|
item-key="key"
|
||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
>
|
>
|
||||||
<template #item="{ element: item, index }">
|
<template #item="{ element: item, index }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div
|
<div
|
||||||
class="aksrt-sortable__head"
|
class="jio-sortable__head"
|
||||||
v-bind="item.fields?.length ? listItemHeadAttrs(item.key) : {}"
|
v-bind="item.fields?.length ? listItemHeadAttrs(item.key) : {}"
|
||||||
>
|
>
|
||||||
<AksSortHandle @click.stop />
|
<JioSortHandle @click.stop />
|
||||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||||
<span class="aksrt-sortable__main">
|
<span class="jio-sortable__main">
|
||||||
<span class="aksrt-sortable__title">{{ item.label }}</span>
|
<span class="jio-sortable__title">{{ item.label }}</span>
|
||||||
</span>
|
</span>
|
||||||
<FaIcon
|
<FaIcon
|
||||||
v-if="item.fields?.length"
|
v-if="item.fields?.length"
|
||||||
:icon="isListItemOpen(item.key) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
:icon="isListItemOpen(item.key) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||||
:size="12"
|
:size="12"
|
||||||
class="aksrt-sortable__toggle"
|
class="jio-sortable__toggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.fields?.length" v-show="isListItemOpen(item.key)" class="aksrt-sortable__body aksrt-field__sortable-body">
|
<div v-if="item.fields?.length" v-show="isListItemOpen(item.key)" class="jio-sortable__body jio-field__sortable-body">
|
||||||
<AksField v-for="field in item.fields" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" :form-data="formData" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
<JioField v-for="field in item.fields" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" :form-data="formData" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
|
|
||||||
<!-- Repeater (array of objects) — inline expand (default, draggable) -->
|
<!-- Repeater (array of objects) — inline expand (default, draggable) -->
|
||||||
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
|
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="jio-field__repeater">
|
||||||
<draggable
|
<draggable
|
||||||
v-if="repeaterDragList.length"
|
v-if="repeaterDragList.length"
|
||||||
v-model="repeaterDragList"
|
v-model="repeaterDragList"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
:item-key="repeaterDragKey"
|
:item-key="repeaterDragKey"
|
||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head" v-bind="repeaterHeadAttrs(repeaterDragKey(element))">
|
<div class="jio-sortable__head" v-bind="repeaterHeadAttrs(repeaterDragKey(element))">
|
||||||
<AksSortHandle @click.stop />
|
<JioSortHandle @click.stop />
|
||||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||||
<span class="aksrt-sortable__main">
|
<span class="jio-sortable__main">
|
||||||
<span class="aksrt-sortable__title aksrt-field__repeater-summary">
|
<span class="jio-sortable__title jio-field__repeater-summary">
|
||||||
{{ repeaterItemSummary(element) || '未填写' }}
|
{{ repeaterItemSummary(element) || '未填写' }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="aksrt-sortable__actions" @click.stop>
|
<div class="jio-sortable__actions" @click.stop>
|
||||||
<n-button
|
<n-button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
type="error"
|
type="error"
|
||||||
@@ -447,12 +451,12 @@
|
|||||||
<FaIcon
|
<FaIcon
|
||||||
:icon="isRepeaterItemOpen(repeaterDragKey(element)) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
:icon="isRepeaterItemOpen(repeaterDragKey(element)) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||||
:size="12"
|
:size="12"
|
||||||
class="aksrt-sortable__toggle"
|
class="jio-sortable__toggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="isRepeaterItemOpen(repeaterDragKey(element))" class="aksrt-sortable__body aksrt-field__repeater-fields">
|
<div v-show="isRepeaterItemOpen(repeaterDragKey(element))" class="jio-sortable__body jio-field__repeater-fields">
|
||||||
<template v-for="field in (schema.fields || [])" :key="field.key">
|
<template v-for="field in (schema.fields || [])" :key="field.key">
|
||||||
<AksField
|
<JioField
|
||||||
v-if="matchDependency(field.dependsOn, element)"
|
v-if="matchDependency(field.dependsOn, element)"
|
||||||
:schema="field"
|
:schema="field"
|
||||||
:model-value="element[field.key]"
|
:model-value="element[field.key]"
|
||||||
@@ -465,7 +469,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<div v-else class="aksrt-sortable__empty">
|
<div v-else class="jio-sortable__empty">
|
||||||
<FaIcon icon="fa fas fa-inbox" :size="18" />
|
<FaIcon icon="fa fas fa-inbox" :size="18" />
|
||||||
<span>{{ schema.emptyText || '暂无数据' }}</span>
|
<span>{{ schema.emptyText || '暂无数据' }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -482,7 +486,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Repeater list-modal: draggable card list + modal editor -->
|
<!-- Repeater list-modal: draggable card list + modal editor -->
|
||||||
<AksRepeaterListModal
|
<JioRepeaterListModal
|
||||||
v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui === 'list-modal'"
|
v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui === 'list-modal'"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@@ -502,35 +506,35 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Validation message -->
|
<!-- Validation message -->
|
||||||
<p v-if="errorMessage" class="aksrt-field__error">{{ errorMessage }}</p>
|
<p v-if="errorMessage" class="jio-field__error">{{ errorMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* AksField — renders a single form field based on its schema definition.
|
* JioField — renders a single form field based on its schema definition.
|
||||||
*
|
*
|
||||||
* Supported types: switch, input/text, textarea, number, select, radio,
|
* Supported types: switch, input/text, textarea, number, select, radio,
|
||||||
* checkbox, color, icon, media, gallery, slider, code, alert, fieldset, sortable,
|
* checkbox, color, icon, media, gallery, slider, code, alert, fieldset, sortable,
|
||||||
* repeater (inline|list-modal), custom (slot).
|
* repeater (inline|list-modal), custom (slot).
|
||||||
*
|
*
|
||||||
* 所有可拖拽列表(sortable / 已选值排序 / repeater)共用 sortable-list.css +
|
* 所有可拖拽列表(sortable / 已选值排序 / repeater)共用 sortable-list.css +
|
||||||
* SORTABLE_LIST_ATTRS + AksSortHandle。
|
* SORTABLE_LIST_ATTRS + JioSortHandle。
|
||||||
*/
|
*/
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import FaIcon from './FaIcon.vue'
|
import FaIcon from './FaIcon.vue'
|
||||||
import FaIconPicker from './FaIconPicker.vue'
|
import FaIconPicker from './FaIconPicker.vue'
|
||||||
import AksMediaField from './AksMediaField.vue'
|
import JioMediaField from './JioMediaField.vue'
|
||||||
import AksGalleryField from './AksGalleryField.vue'
|
import JioGalleryField from './JioGalleryField.vue'
|
||||||
import AksCodeEditor from './AksCodeEditor.vue'
|
import JioCodeEditor from './JioCodeEditor.vue'
|
||||||
import AksCompositeField from './AksCompositeField.vue'
|
import JioCompositeField from './JioCompositeField.vue'
|
||||||
import AksColorPicker from './AksColorPicker.vue'
|
import JioColorPicker from './JioColorPicker.vue'
|
||||||
import AksImageSelectField from './AksImageSelectField.vue'
|
import JioImageSelectField from './JioImageSelectField.vue'
|
||||||
import AksSortHandle from './AksSortHandle.vue'
|
import JioSortHandle from './JioSortHandle.vue'
|
||||||
import AksSecretClear from './AksSecretClear.vue'
|
import JioSecretClear from './JioSecretClear.vue'
|
||||||
import AksSelectedOrder from './AksSelectedOrder.vue'
|
import JioSelectedOrder from './JioSelectedOrder.vue'
|
||||||
import AksRepeaterListModal from './AksRepeaterListModal.vue'
|
import JioRepeaterListModal from './JioRepeaterListModal.vue'
|
||||||
import { buildFieldsetSections } from './fieldset-sections.js'
|
import { buildFieldsetSections } from './fieldset-sections.js'
|
||||||
import { matchDependency } from './field-dependency.js'
|
import { matchDependency } from './field-dependency.js'
|
||||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||||
@@ -566,20 +570,39 @@ const {
|
|||||||
} = useFieldRemoteOptions(props)
|
} = useFieldRemoteOptions(props)
|
||||||
|
|
||||||
const fieldClasses = computed(() => ({
|
const fieldClasses = computed(() => ({
|
||||||
'aksrt-field--disabled': props.schema.disabled,
|
'jio-field--disabled': props.schema.disabled,
|
||||||
'aksrt-field--inline': props.schema.inline,
|
'jio-field--inline': props.schema.inline,
|
||||||
[`aksrt-field--${props.schema.type}`]: !!props.schema.type,
|
[`jio-field--${props.schema.type}`]: !!props.schema.type,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const secretMarkedForClear = computed(() => props.schema.secret && props.modelValue === '__AKSRT_CLEAR_SECRET__')
|
/**
|
||||||
|
* 凭证字段不向浏览器暴露成密码输入。
|
||||||
|
*
|
||||||
|
* 设置页往往一次渲染二十多个 AppSecret/私钥,只要页面上出现 type="password",
|
||||||
|
* Chrome 就会把整页判定为登录表单,并把保存的账号密码灌进第一个文本框
|
||||||
|
* (顶部的设置项搜索框)。这里把凭证字段降级为 text 并用 CSS 的
|
||||||
|
* -webkit-text-security 做遮蔽:视觉上仍是密文,但浏览器不再识别成密码框。
|
||||||
|
*/
|
||||||
|
const secretMarkedForClear = computed(() => props.schema.secret && props.modelValue === '__JIO_CLEAR_SECRET__')
|
||||||
const secretDisplayValue = computed(() => secretMarkedForClear.value ? '' : props.modelValue)
|
const secretDisplayValue = computed(() => secretMarkedForClear.value ? '' : props.modelValue)
|
||||||
/** 已配置密钥(服务端回传 '***'):textarea 不回显掩码,留空即保持原值。 */
|
/** 已配置密钥(服务端回传 '***'):textarea 不回显掩码,留空即保持原值。 */
|
||||||
const secretConfigured = computed(() => props.schema.secret && props.modelValue === '***')
|
const secretConfigured = computed(() => props.schema.secret && props.modelValue === '***')
|
||||||
const secretClearVisible = computed(() => secretConfigured.value || secretMarkedForClear.value)
|
const secretClearVisible = computed(() => secretConfigured.value || secretMarkedForClear.value)
|
||||||
const secretTextareaValue = computed(() => secretConfigured.value ? '' : secretDisplayValue.value)
|
const secretTextareaValue = computed(() => secretConfigured.value ? '' : secretDisplayValue.value)
|
||||||
|
|
||||||
|
/** 凭证字段的输入属性:彻底关掉自动填充与密码管理器的识别。 */
|
||||||
|
const secretInputProps = {
|
||||||
|
autocomplete: 'off',
|
||||||
|
autocorrect: 'off',
|
||||||
|
autocapitalize: 'off',
|
||||||
|
spellcheck: 'false',
|
||||||
|
'data-lpignore': 'true',
|
||||||
|
'data-1p-ignore': 'true',
|
||||||
|
'data-form-type': 'other',
|
||||||
|
}
|
||||||
|
|
||||||
function toggleSecretClear() {
|
function toggleSecretClear() {
|
||||||
emit('update:modelValue', secretMarkedForClear.value ? '***' : '__AKSRT_CLEAR_SECRET__')
|
emit('update:modelValue', secretMarkedForClear.value ? '***' : '__JIO_CLEAR_SECRET__')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Normalize options: support string[], {label, value}[], and dynamic formData-key references */
|
/** Normalize options: support string[], {label, value}[], and dynamic formData-key references */
|
||||||
@@ -681,7 +704,7 @@ function updateSortable(itemKey, fieldKey, value) {
|
|||||||
emit('update:modelValue', { ...current, [itemKey]: { ...(current[itemKey] || {}), [fieldKey]: value } })
|
emit('update:modelValue', { ...current, [itemKey]: { ...(current[itemKey] || {}), [fieldKey]: value } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ Repeater(inline 展开;list-modal 见 AksRepeaterListModal) ============
|
// ============ Repeater(inline 展开;list-modal 见 JioRepeaterListModal) ============
|
||||||
const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0)
|
const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0)
|
||||||
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
|
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
|
||||||
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
|
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
|
||||||
@@ -753,165 +776,165 @@ function updateRepeaterItem(index, fieldKey, value) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-field {
|
.jio-field {
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field:last-child {
|
.jio-field:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 结构化分组(会员商品/续费商品/积分兑换/升级价格)与相邻字段之间留出更明确的间隔,
|
/* 结构化分组(会员商品/续费商品/积分兑换/升级价格)与相邻字段之间留出更明确的间隔,
|
||||||
* 避免两个分组上下贴在一起,和参考主题的留白一致。 */
|
* 避免两个分组上下贴在一起,和参考主题的留白一致。 */
|
||||||
.aksrt-field--group,
|
.jio-field--group,
|
||||||
.aksrt-field--repeater,
|
.jio-field--repeater,
|
||||||
.aksrt-field--accordion,
|
.jio-field--accordion,
|
||||||
.aksrt-field--fieldset {
|
.jio-field--fieldset {
|
||||||
margin-bottom: 26px;
|
margin-bottom: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__input-wrap {
|
.jio-field__input-wrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__label-row {
|
.jio-field__label-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__label {
|
.jio-field__label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__required {
|
.jio-field__required {
|
||||||
color: #d03050;
|
color: #d03050;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__help-icon {
|
.jio-field__help-icon {
|
||||||
color: var(--aksrt-color-border-strong);
|
color: var(--jio-color-border-strong);
|
||||||
cursor: help;
|
cursor: help;
|
||||||
transition: color 0.15s;
|
transition: color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__help-icon:hover {
|
.jio-field__help-icon:hover {
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__desc {
|
.jio-field__desc {
|
||||||
margin: -2px 0 8px;
|
margin: -2px 0 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__error {
|
.jio-field__error {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #d03050;
|
color: #d03050;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field--disabled {
|
.jio-field--disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__number {
|
.jio-field__number {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__unit {
|
.jio-field__unit {
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__palette {
|
.jio-field__palette {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__palette-item {
|
.jio-field__palette-item {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 3px solid var(--aksrt-color-surface);
|
border: 3px solid var(--jio-color-surface);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 0 0 0 1px var(--aksrt-color-border-strong);
|
box-shadow: 0 0 0 1px var(--jio-color-border-strong);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__palette-item.is-active {
|
.jio-field__palette-item.is-active {
|
||||||
box-shadow: 0 0 0 2px var(--aksrt-color-primary);
|
box-shadow: 0 0 0 2px var(--jio-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__link {
|
.jio-field__link {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(220px, 1fr) 120px minmax(140px, .5fr);
|
grid-template-columns: minmax(220px, 1fr) 120px minmax(140px, .5fr);
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sortable 卡片展开区:两列字段栅格(卡片本身见 styles/sortable-list.css) */
|
/* sortable 卡片展开区:两列字段栅格(卡片本身见 styles/sortable-list.css) */
|
||||||
.aksrt-field__sortable-body {
|
.jio-field__sortable-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fieldset / Accordion */
|
/* Fieldset / Accordion */
|
||||||
.aksrt-field__fieldset {
|
.jio-field__fieldset {
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__accordion-section + .aksrt-field__accordion-section {
|
.jio-field__accordion-section + .jio-field__accordion-section {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__accordion-section-head {
|
.jio-field__accordion-section-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: var(--aksrt-color-surface);
|
background: var(--jio-color-surface);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
transition: border-color 0.15s, color 0.15s;
|
transition: border-color 0.15s, color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__accordion-section-head:hover {
|
.jio-field__accordion-section-head:hover {
|
||||||
border-color: var(--aksrt-color-primary);
|
border-color: var(--jio-color-primary);
|
||||||
background: var(--aksrt-color-primary-soft);
|
background: var(--jio-color-primary-soft);
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__accordion-section-title {
|
.jio-field__accordion-section-title {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__accordion-section-toggle {
|
.jio-field__accordion-section-toggle {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__accordion-section-body {
|
.jio-field__accordion-section-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -919,58 +942,58 @@ function updateRepeaterItem(index, fieldKey, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Repeater inline(卡片 / 手柄 / 拖拽态见 styles/sortable-list.css) */
|
/* Repeater inline(卡片 / 手柄 / 拖拽态见 styles/sortable-list.css) */
|
||||||
.aksrt-field__repeater {
|
.jio-field__repeater {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__repeater-fields {
|
.jio-field__repeater-fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content field */
|
/* Content field */
|
||||||
.aksrt-field__content {
|
.jio-field__content {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__content :deep(p) {
|
.jio-field__content :deep(p) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__content :deep(p + ul) {
|
.jio-field__content :deep(p + ul) {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__content :deep(ul) {
|
.jio-field__content :deep(ul) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
line-height: 1.9;
|
line-height: 1.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 说明文字的小号灰色段落与警示文字(schema content 里用 class 引用) */
|
/* 说明文字的小号灰色段落与警示文字(schema content 里用 class 引用) */
|
||||||
.aksrt-field__content :deep(.aksrt-hint) {
|
.jio-field__content :deep(.jio-hint) {
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__content :deep(.aksrt-hint + .aksrt-hint) {
|
.jio-field__content :deep(.jio-hint + .jio-hint) {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field__content :deep(.aksrt-warn) {
|
.jio-field__content :deep(.jio-warn) {
|
||||||
color: var(--aksrt-color-warning, #d97706);
|
color: var(--jio-color-warning, #d97706);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.aksrt-field__link,
|
.jio-field__link,
|
||||||
.aksrt-field__sortable-body {
|
.jio-field__sortable-body {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+43
-43
@@ -1,26 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-field-group" :class="{ 'is-collapsible': collapsible, 'is-collapsed': collapsible && collapsed }">
|
<div class="jio-field-group" :class="{ 'is-collapsible': collapsible, 'is-collapsed': collapsible && collapsed }">
|
||||||
<div
|
<div
|
||||||
v-if="title"
|
v-if="title"
|
||||||
class="aksrt-field-group__header"
|
class="jio-field-group__header"
|
||||||
:class="{ 'is-clickable': collapsible }"
|
:class="{ 'is-clickable': collapsible }"
|
||||||
@click="collapsible ? toggleCollapse() : undefined"
|
@click="collapsible ? toggleCollapse() : undefined"
|
||||||
>
|
>
|
||||||
<div class="aksrt-field-group__header-left">
|
<div class="jio-field-group__header-left">
|
||||||
<FaIcon v-if="icon" :icon="icon" :size="16" class="aksrt-field-group__icon" />
|
<FaIcon v-if="icon" :icon="icon" :size="16" class="jio-field-group__icon" />
|
||||||
<div>
|
<div>
|
||||||
<h3 class="aksrt-field-group__title">{{ title }}</h3>
|
<h3 class="jio-field-group__title">{{ title }}</h3>
|
||||||
<p v-if="description" class="aksrt-field-group__desc">{{ description }}</p>
|
<p v-if="description" class="jio-field-group__desc">{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-field-group__header-right">
|
<div class="jio-field-group__header-right">
|
||||||
<div v-if="$slots.actions" class="aksrt-field-group__actions" @click.stop>
|
<div v-if="$slots.actions" class="jio-field-group__actions" @click.stop>
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="collapsible"
|
v-if="collapsible"
|
||||||
type="button"
|
type="button"
|
||||||
class="aksrt-field-group__collapse-btn"
|
class="jio-field-group__collapse-btn"
|
||||||
:aria-expanded="(!collapsed).toString()"
|
:aria-expanded="(!collapsed).toString()"
|
||||||
:title="collapsed ? '展开配置' : '收起配置'"
|
:title="collapsed ? '展开配置' : '收起配置'"
|
||||||
@click.stop="toggleCollapse"
|
@click.stop="toggleCollapse"
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="aksrt-collapse">
|
<transition name="jio-collapse">
|
||||||
<div v-show="!collapsed" class="aksrt-field-group__body">
|
<div v-show="!collapsed" class="jio-field-group__body">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* AksFieldGroup — card-style container for grouping related form fields.
|
* JioFieldGroup — card-style container for grouping related form fields.
|
||||||
* Supports collapsible sections, icon headers, and description text.
|
* Supports collapsible sections, icon headers, and description text.
|
||||||
*
|
*
|
||||||
* defaultCollapsed only seeds the initial state; afterwards the user toggles freely.
|
* defaultCollapsed only seeds the initial state; afterwards the user toggles freely.
|
||||||
@@ -69,16 +69,16 @@ function toggleCollapse() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-field-group {
|
.jio-field-group {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: var(--aksrt-color-surface);
|
background: var(--jio-color-surface);
|
||||||
box-shadow: var(--aksrt-shadow-card);
|
box-shadow: var(--jio-shadow-card);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__header {
|
.jio-field-group__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -86,66 +86,66 @@ function toggleCollapse() {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__header.is-clickable {
|
.jio-field-group__header.is-clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__header.is-clickable:hover .aksrt-field-group__title {
|
.jio-field-group__header.is-clickable:hover .jio-field-group__title {
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group.is-collapsed .aksrt-field-group__header {
|
.jio-field-group.is-collapsed .jio-field-group__header {
|
||||||
padding-bottom: 18px;
|
padding-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__header-left {
|
.jio-field-group__header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__header-right {
|
.jio-field-group__header-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__icon {
|
.jio-field-group__icon {
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__title {
|
.jio-field-group__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
transition: color 0.15s;
|
transition: color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__desc {
|
.jio-field-group__desc {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__actions {
|
.jio-field-group__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__collapse-btn {
|
.jio-field-group__collapse-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -153,25 +153,25 @@ function toggleCollapse() {
|
|||||||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__collapse-btn:hover {
|
.jio-field-group__collapse-btn:hover {
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
border-color: var(--aksrt-color-primary);
|
border-color: var(--jio-color-primary);
|
||||||
background: var(--aksrt-color-primary-soft);
|
background: var(--jio-color-primary-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-field-group__body {
|
.jio-field-group__body {
|
||||||
padding: 16px 20px 20px;
|
padding: 16px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collapse transition */
|
/* Collapse transition */
|
||||||
.aksrt-collapse-enter-active,
|
.jio-collapse-enter-active,
|
||||||
.aksrt-collapse-leave-active {
|
.jio-collapse-leave-active {
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-collapse-enter-from,
|
.jio-collapse-enter-from,
|
||||||
.aksrt-collapse-leave-to {
|
.jio-collapse-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
+53
-39
@@ -1,20 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-form-renderer">
|
<div class="jio-form-renderer">
|
||||||
<!-- Page header with title and description -->
|
<!-- Page header with title and description -->
|
||||||
<div v-if="title" class="aksrt-form-renderer__header">
|
<div v-if="title" class="jio-form-renderer__header">
|
||||||
<div class="aksrt-form-renderer__header-left">
|
<div class="jio-form-renderer__header-left">
|
||||||
<FaIcon v-if="icon" :icon="icon" :size="22" class="aksrt-form-renderer__header-icon" />
|
<FaIcon v-if="icon" :icon="icon" :size="22" class="jio-form-renderer__header-icon" />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="aksrt-form-renderer__title">{{ title }}</h1>
|
<h1 class="jio-form-renderer__title">{{ title }}</h1>
|
||||||
<p v-if="description" class="aksrt-form-renderer__desc">{{ description }}</p>
|
<p v-if="description" class="jio-form-renderer__desc">{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showSearch" class="aksrt-form-renderer__search">
|
<div v-if="showSearch" class="jio-form-renderer__search">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="searchQuery"
|
v-model:value="searchQuery"
|
||||||
placeholder="搜索设置项..."
|
placeholder="搜索设置项..."
|
||||||
size="small"
|
size="small"
|
||||||
clearable
|
clearable
|
||||||
|
name="jio-settings-search"
|
||||||
|
:input-props="searchInputProps"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<FaIcon icon="fa fas fa-search" :size="12" />
|
<FaIcon icon="fa fas fa-search" :size="12" />
|
||||||
@@ -24,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading state -->
|
<!-- Loading state -->
|
||||||
<div v-if="loading" class="aksrt-form-renderer__loading">
|
<div v-if="loading" class="jio-form-renderer__loading">
|
||||||
<n-spin size="medium" />
|
<n-spin size="medium" />
|
||||||
<span>加载中...</span>
|
<span>加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
<!-- Form content -->
|
<!-- Form content -->
|
||||||
<template v-else-if="schema && schema.length">
|
<template v-else-if="schema && schema.length">
|
||||||
<template v-for="group in filteredSchema" :key="groupKey(group)">
|
<template v-for="group in filteredSchema" :key="groupKey(group)">
|
||||||
<AksFieldGroup
|
<JioFieldGroup
|
||||||
v-if="isGroupVisible(group)"
|
v-if="isGroupVisible(group)"
|
||||||
:title="group.title"
|
:title="group.title"
|
||||||
:description="group.description"
|
:description="group.description"
|
||||||
@@ -59,7 +61,7 @@
|
|||||||
<!-- Tabbed: renders tabs whose sub-fields are flat top-level keys -->
|
<!-- Tabbed: renders tabs whose sub-fields are flat top-level keys -->
|
||||||
<div
|
<div
|
||||||
v-if="field.type === 'tabbed' && (field.tabs || []).length"
|
v-if="field.type === 'tabbed' && (field.tabs || []).length"
|
||||||
class="aksrt-form-renderer__tabbed"
|
class="jio-form-renderer__tabbed"
|
||||||
>
|
>
|
||||||
<n-tabs type="line" animated>
|
<n-tabs type="line" animated>
|
||||||
<n-tab-pane
|
<n-tab-pane
|
||||||
@@ -69,7 +71,7 @@
|
|||||||
:tab="tabName(tab)"
|
:tab="tabName(tab)"
|
||||||
>
|
>
|
||||||
<template v-for="sub in (tab.fields || [])" :key="sub.key">
|
<template v-for="sub in (tab.fields || [])" :key="sub.key">
|
||||||
<AksField
|
<JioField
|
||||||
v-if="isFieldVisible(sub)"
|
v-if="isFieldVisible(sub)"
|
||||||
:schema="sub"
|
:schema="sub"
|
||||||
:model-value="getFieldValue(sub.key)"
|
:model-value="getFieldValue(sub.key)"
|
||||||
@@ -82,7 +84,7 @@
|
|||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
<AksSorterField
|
<JioSorterField
|
||||||
v-else-if="field.type === 'custom' && field.renderer === 'sorter'"
|
v-else-if="field.type === 'custom' && field.renderer === 'sorter'"
|
||||||
:model-value="getFieldValue(field.key) || {}"
|
:model-value="getFieldValue(field.key) || {}"
|
||||||
:options="field.options || []"
|
:options="field.options || []"
|
||||||
@@ -93,7 +95,7 @@
|
|||||||
:name="`field-${field.key}`"
|
:name="`field-${field.key}`"
|
||||||
v-bind="{ field, value: getFieldValue(field.key), setValue: (value) => setFieldValue(field.key, value), formData }"
|
v-bind="{ field, value: getFieldValue(field.key), setValue: (value) => setFieldValue(field.key, value), formData }"
|
||||||
>
|
>
|
||||||
<AksField
|
<JioField
|
||||||
:schema="field"
|
:schema="field"
|
||||||
:model-value="getFieldValue(field.key)"
|
:model-value="getFieldValue(field.key)"
|
||||||
:form-data="formData"
|
:form-data="formData"
|
||||||
@@ -102,7 +104,7 @@
|
|||||||
@action="emit('field-action', $event)"
|
@action="emit('field-action', $event)"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
<AksField
|
<JioField
|
||||||
v-else
|
v-else
|
||||||
:schema="field"
|
:schema="field"
|
||||||
:model-value="getFieldValue(field.key)"
|
:model-value="getFieldValue(field.key)"
|
||||||
@@ -117,24 +119,24 @@
|
|||||||
|
|
||||||
<!-- Custom group content slot -->
|
<!-- Custom group content slot -->
|
||||||
<slot :name="`group-${group.key}`" v-bind="{ group, formData }" />
|
<slot :name="`group-${group.key}`" v-bind="{ group, formData }" />
|
||||||
</AksFieldGroup>
|
</JioFieldGroup>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
<div v-else class="aksrt-form-renderer__empty">
|
<div v-else class="jio-form-renderer__empty">
|
||||||
<FaIcon icon="fa fas fa-cog" :size="32" />
|
<FaIcon icon="fa fas fa-cog" :size="32" />
|
||||||
<p>暂无配置项</p>
|
<p>暂无配置项</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sticky save bar -->
|
<!-- Sticky save bar -->
|
||||||
<AksSaveBar :dirty="dirty" :saving="saving" @save="$emit('save')" @reload="$emit('reload')" />
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="$emit('save')" @reload="$emit('reload')" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* AksFormRenderer — the core schema-driven form engine.
|
* JioFormRenderer — the core schema-driven form engine.
|
||||||
*
|
*
|
||||||
* Takes a schema definition (array of field groups) and a reactive form data object,
|
* Takes a schema definition (array of field groups) and a reactive form data object,
|
||||||
* and renders a complete settings page with:
|
* and renders a complete settings page with:
|
||||||
@@ -146,10 +148,10 @@
|
|||||||
*/
|
*/
|
||||||
import { ref, computed, useSlots } from 'vue'
|
import { ref, computed, useSlots } from 'vue'
|
||||||
import FaIcon from './FaIcon.vue'
|
import FaIcon from './FaIcon.vue'
|
||||||
import AksSaveBar from './AksSaveBar.vue'
|
import JioSaveBar from './JioSaveBar.vue'
|
||||||
import AksFieldGroup from './AksFieldGroup.vue'
|
import JioFieldGroup from './JioFieldGroup.vue'
|
||||||
import AksField from './AksField.vue'
|
import JioField from './JioField.vue'
|
||||||
import AksSorterField from './AksSorterField.vue'
|
import JioSorterField from './JioSorterField.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/** Page title */
|
/** Page title */
|
||||||
@@ -185,6 +187,18 @@ const slots = useSlots()
|
|||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
// 同一页面上存在大量 type="password" 的凭证字段,Chrome 会把整页当成登录
|
||||||
|
// 表单,并把保存的账号灌进第一个文本框(也就是这个搜索框)。改名成搜索语义,
|
||||||
|
// 并显式关掉各类自动填充,才能让浏览器不再识别它。
|
||||||
|
const searchInputProps = {
|
||||||
|
autocomplete: 'off',
|
||||||
|
autocorrect: 'off',
|
||||||
|
autocapitalize: 'off',
|
||||||
|
spellcheck: 'false',
|
||||||
|
'data-lpignore': 'true',
|
||||||
|
'data-1p-ignore': 'true',
|
||||||
|
}
|
||||||
|
|
||||||
/** Evaluate dependsOn rule against current form data */
|
/** Evaluate dependsOn rule against current form data */
|
||||||
function matchDependsOn(dependsOn) {
|
function matchDependsOn(dependsOn) {
|
||||||
if (!dependsOn) return true
|
if (!dependsOn) return true
|
||||||
@@ -308,7 +322,7 @@ const filteredSchema = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-form-renderer__header {
|
.jio-form-renderer__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -317,73 +331,73 @@ const filteredSchema = computed(() => {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__header-left {
|
.jio-form-renderer__header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__header-icon {
|
.jio-form-renderer__header-icon {
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__title {
|
.jio-form-renderer__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__desc {
|
.jio-form-renderer__desc {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__tabbed {
|
.jio-form-renderer__tabbed {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__search {
|
.jio-form-renderer__search {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__loading {
|
.jio-form-renderer__loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 60px 0;
|
padding: 60px 0;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__empty {
|
.jio-form-renderer__empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 60px 0;
|
padding: 60px 0;
|
||||||
color: var(--aksrt-color-border-strong);
|
color: var(--jio-color-border-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__empty p {
|
.jio-form-renderer__empty p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.aksrt-form-renderer__header {
|
.jio-form-renderer__header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__search {
|
.jio-form-renderer__search {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
+23
-23
@@ -1,23 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-gallery-field">
|
<div class="jio-gallery-field">
|
||||||
<div v-if="attachmentIds.length" class="aksrt-gallery-field__grid">
|
<div v-if="attachmentIds.length" class="jio-gallery-field__grid">
|
||||||
<article
|
<article
|
||||||
v-for="(attachmentId, index) in attachmentIds"
|
v-for="(attachmentId, index) in attachmentIds"
|
||||||
:key="attachmentId"
|
:key="attachmentId"
|
||||||
class="aksrt-gallery-field__item"
|
class="jio-gallery-field__item"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="previews[attachmentId]?.url"
|
v-if="previews[attachmentId]?.url"
|
||||||
:src="previews[attachmentId].url"
|
:src="previews[attachmentId].url"
|
||||||
:alt="previews[attachmentId].alt || `附件 ${attachmentId}`"
|
:alt="previews[attachmentId].alt || `附件 ${attachmentId}`"
|
||||||
class="aksrt-gallery-field__image"
|
class="jio-gallery-field__image"
|
||||||
/>
|
/>
|
||||||
<div v-else class="aksrt-gallery-field__placeholder">
|
<div v-else class="jio-gallery-field__placeholder">
|
||||||
<FaIcon icon="fa fas fa-image" :size="22" />
|
<FaIcon icon="fa fas fa-image" :size="22" />
|
||||||
<span>#{{ attachmentId }}</span>
|
<span>#{{ attachmentId }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="aksrt-gallery-field__actions">
|
<div class="jio-gallery-field__actions">
|
||||||
<n-button
|
<n-button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
quaternary
|
quaternary
|
||||||
@@ -49,12 +49,12 @@
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="aksrt-gallery-field__empty">
|
<div v-else class="jio-gallery-field__empty">
|
||||||
<FaIcon icon="fa fas fa-images" :size="28" />
|
<FaIcon icon="fa fas fa-images" :size="28" />
|
||||||
<span>尚未选择图片</span>
|
<span>尚未选择图片</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="aksrt-gallery-field__buttons">
|
<div class="jio-gallery-field__buttons">
|
||||||
<n-button type="primary" secondary @click="openMediaLibrary">
|
<n-button type="primary" secondary @click="openMediaLibrary">
|
||||||
<FaIcon icon="fa fas fa-plus" />
|
<FaIcon icon="fa fas fa-plus" />
|
||||||
{{ attachmentIds.length ? '编辑图片' : '选择图片' }}
|
{{ attachmentIds.length ? '编辑图片' : '选择图片' }}
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="mediaError" class="aksrt-gallery-field__error">{{ mediaError }}</p>
|
<p v-if="mediaError" class="jio-gallery-field__error">{{ mediaError }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -172,25 +172,25 @@ watch(attachmentIds, hydratePreviews, { immediate: true })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-gallery-field__grid {
|
.jio-gallery-field__grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__item {
|
.jio-gallery-field__item {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 4px 4px 0;
|
padding: 4px 4px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__image {
|
.jio-gallery-field__image {
|
||||||
display: block;
|
display: block;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -199,7 +199,7 @@ watch(attachmentIds, hydratePreviews, { immediate: true })
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__placeholder {
|
.jio-gallery-field__placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
@@ -207,35 +207,35 @@ watch(attachmentIds, hydratePreviews, { immediate: true })
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__actions {
|
.jio-gallery-field__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border-top: 1px solid var(--aksrt-color-border);
|
border-top: 1px solid var(--jio-color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__empty {
|
.jio-gallery-field__empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 116px;
|
min-height: 116px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border: 1px dashed var(--aksrt-color-border-strong);
|
border: 1px dashed var(--jio-color-border-strong);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__buttons {
|
.jio-gallery-field__buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-gallery-field__error {
|
.jio-gallery-field__error {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
color: #d03050;
|
color: #d03050;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<div class="jio-image-select" role="radiogroup">
|
||||||
|
<button
|
||||||
|
v-for="opt in options"
|
||||||
|
:key="opt.value"
|
||||||
|
type="button"
|
||||||
|
role="radio"
|
||||||
|
class="jio-image-select__item"
|
||||||
|
:class="{ 'is-active': modelValue === opt.value }"
|
||||||
|
:aria-checked="modelValue === opt.value"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="emit('update:modelValue', opt.value)"
|
||||||
|
>
|
||||||
|
<img v-if="opt.image" :src="opt.image" :alt="opt.label" class="jio-image-select__img" :style="imgStyle(opt)" />
|
||||||
|
<span v-else class="jio-image-select__placeholder" :style="imgStyle(opt)">
|
||||||
|
<FaIcon :icon="opt.icon || 'fa fas fa-image'" :size="20" />
|
||||||
|
</span>
|
||||||
|
<span class="jio-image-select__label">{{ opt.label }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/** image_select:带预览图的单选。 */
|
||||||
|
import FaIcon from './FaIcon.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { default: null },
|
||||||
|
options: { type: Array, default: () => [] },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
// Preview width in px. Layout strips (header/footer) are very wide and only
|
||||||
|
// need enough width to be legible; near-square previews want more room.
|
||||||
|
previewWidth: { type: [Number, String], default: 140 },
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// An option may override the group width via its own `width`.
|
||||||
|
function imgStyle(opt) {
|
||||||
|
const width = opt && opt.width ? opt.width : props.previewWidth
|
||||||
|
return { '--jio-image-select-max': typeof width === 'number' ? `${width}px` : width }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.jio-image-select {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 80px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 2px solid var(--jio-color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--jio-color-surface);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color .15s ease, background-color .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__item:hover,
|
||||||
|
.jio-image-select__item:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--jio-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__item.is-active {
|
||||||
|
border-color: var(--jio-color-primary);
|
||||||
|
background: var(--jio-color-primary-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__item:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__img {
|
||||||
|
display: block;
|
||||||
|
/* Preview images are wide layout strips (header 597x57, footer 800x190, slide
|
||||||
|
align 200x101), so let them keep their own aspect ratio inside the card.
|
||||||
|
A fixed square box with object-fit:cover crops the layout to an
|
||||||
|
unrecognisable sliver — the width is capped instead and the height follows. */
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--jio-image-select-max, 140px);
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--jio-color-canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: var(--jio-image-select-max, 140px);
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--jio-color-canvas);
|
||||||
|
color: var(--jio-color-border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-image-select__label {
|
||||||
|
color: var(--jio-color-text-secondary);
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.jio-image-select__item {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+27
-27
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-media-field">
|
<div class="jio-media-field">
|
||||||
<div v-if="modelValue" class="aksrt-media-field__preview">
|
<div v-if="modelValue" class="jio-media-field__preview">
|
||||||
<img
|
<img
|
||||||
v-if="isImage"
|
v-if="isImage"
|
||||||
:src="modelValue"
|
:src="modelValue"
|
||||||
:alt="placeholder"
|
:alt="placeholder"
|
||||||
class="aksrt-media-field__image"
|
class="jio-media-field__image"
|
||||||
/>
|
/>
|
||||||
<div v-else class="aksrt-media-field__file">
|
<div v-else class="jio-media-field__file">
|
||||||
<FaIcon icon="fa fas fa-file" :size="24" />
|
<FaIcon icon="fa fas fa-file" :size="24" />
|
||||||
<span class="aksrt-media-field__filename">{{ fileName }}</span>
|
<span class="jio-media-field__filename">{{ fileName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-media-field__overlay">
|
<div class="jio-media-field__overlay">
|
||||||
<n-button size="tiny" secondary circle title="更换" @click="openMediaLibrary">
|
<n-button size="tiny" secondary circle title="更换" @click="openMediaLibrary">
|
||||||
<FaIcon icon="fa fas fa-exchange-alt" />
|
<FaIcon icon="fa fas fa-exchange-alt" />
|
||||||
</n-button>
|
</n-button>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="aksrt-media-field__empty" @click="openMediaLibrary">
|
<div v-else class="jio-media-field__empty" @click="openMediaLibrary">
|
||||||
<FaIcon icon="fa fas fa-cloud-upload-alt" :size="28" />
|
<FaIcon icon="fa fas fa-cloud-upload-alt" :size="28" />
|
||||||
<span>{{ placeholder }}</span>
|
<span>{{ placeholder }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
:placeholder="'或直接输入 URL'"
|
:placeholder="'或直接输入 URL'"
|
||||||
size="small"
|
size="small"
|
||||||
class="aksrt-media-field__url"
|
class="jio-media-field__url"
|
||||||
@update:value="emit('update:modelValue', $event)"
|
@update:value="emit('update:modelValue', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* AksMediaField — image/file selector with preview.
|
* JioMediaField — image/file selector with preview.
|
||||||
*
|
*
|
||||||
* In the WordPress admin context, this can open the WP Media Library.
|
* In the WordPress admin context, this can open the WP Media Library.
|
||||||
* For now, it supports direct URL input with preview.
|
* For now, it supports direct URL input with preview.
|
||||||
@@ -95,7 +95,7 @@ function clearMedia() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-media-field__preview {
|
.jio-media-field__preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -104,13 +104,13 @@ function clearMedia() {
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__image {
|
.jio-media-field__image {
|
||||||
display: block;
|
display: block;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -119,21 +119,21 @@ function clearMedia() {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__file {
|
.jio-media-field__file {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__filename {
|
.jio-media-field__filename {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--aksrt-color-text-secondary);
|
color: var(--jio-color-text-secondary);
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__overlay {
|
.jio-media-field__overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -144,33 +144,33 @@ function clearMedia() {
|
|||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__preview:hover .aksrt-media-field__overlay {
|
.jio-media-field__preview:hover .jio-media-field__overlay {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__empty {
|
.jio-media-field__empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
border: 2px dashed var(--aksrt-color-border-strong);
|
border: 2px dashed var(--jio-color-border-strong);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__empty:hover {
|
.jio-media-field__empty:hover {
|
||||||
border-color: var(--aksrt-color-primary);
|
border-color: var(--jio-color-primary);
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
background: var(--aksrt-color-primary-soft);
|
background: var(--jio-color-primary-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-media-field__url {
|
.jio-media-field__url {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+23
-23
@@ -1,27 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-repeater-list">
|
<div class="jio-repeater-list">
|
||||||
<draggable
|
<draggable
|
||||||
v-if="rows.length"
|
v-if="rows.length"
|
||||||
v-model="rows"
|
v-model="rows"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
:item-key="rowKey"
|
:item-key="rowKey"
|
||||||
:disabled="schema.disabled"
|
:disabled="schema.disabled"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head">
|
<div class="jio-sortable__head">
|
||||||
<AksSortHandle />
|
<JioSortHandle />
|
||||||
<span class="aksrt-sortable__icon" :class="preview(element).iconClass">
|
<span class="jio-sortable__icon" :class="preview(element).iconClass">
|
||||||
<span v-if="preview(element).customIcon" class="aksrt-repeater-list__icon-html" v-html="preview(element).customIcon" />
|
<span v-if="preview(element).customIcon" class="jio-repeater-list__icon-html" v-html="preview(element).customIcon" />
|
||||||
<i v-else-if="preview(element).icon" :class="preview(element).icon" aria-hidden="true" />
|
<i v-else-if="preview(element).icon" :class="preview(element).icon" aria-hidden="true" />
|
||||||
<FaIcon v-else icon="fa fas fa-star" :size="16" />
|
<FaIcon v-else icon="fa fas fa-star" :size="16" />
|
||||||
</span>
|
</span>
|
||||||
<div class="aksrt-sortable__main">
|
<div class="jio-sortable__main">
|
||||||
<span class="aksrt-sortable__title">{{ preview(element).title }}</span>
|
<span class="jio-sortable__title">{{ preview(element).title }}</span>
|
||||||
<span class="aksrt-sortable__desc">{{ preview(element).desc }}</span>
|
<span class="jio-sortable__desc">{{ preview(element).desc }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-sortable__actions">
|
<div class="jio-sortable__actions">
|
||||||
<n-button size="tiny" quaternary type="primary" :disabled="schema.disabled" @click="openModal(index)">
|
<n-button size="tiny" quaternary type="primary" :disabled="schema.disabled" @click="openModal(index)">
|
||||||
<template #icon><FaIcon icon="fa fas fa-edit" :size="12" /></template>
|
<template #icon><FaIcon icon="fa fas fa-edit" :size="12" /></template>
|
||||||
编辑
|
编辑
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<div v-else class="aksrt-sortable__empty">
|
<div v-else class="jio-sortable__empty">
|
||||||
<FaIcon icon="fa fas fa-inbox" :size="18" />
|
<FaIcon icon="fa fas fa-inbox" :size="18" />
|
||||||
<span>{{ schema.emptyText || '暂无数据' }}</span>
|
<span>{{ schema.emptyText || '暂无数据' }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,11 +50,11 @@
|
|||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
preset="card"
|
preset="card"
|
||||||
:title="editingIndex >= 0 ? (schema.editText || '编辑项目') : (schema.addText || '添加项目')"
|
:title="editingIndex >= 0 ? (schema.editText || '编辑项目') : (schema.addText || '添加项目')"
|
||||||
class="aksrt-modal-card"
|
class="jio-modal-card"
|
||||||
style="max-width: 640px; width: 92vw;"
|
style="max-width: 640px; width: 92vw;"
|
||||||
>
|
>
|
||||||
<div class="aksrt-repeater-list__modal-fields">
|
<div class="jio-repeater-list__modal-fields">
|
||||||
<AksField
|
<JioField
|
||||||
v-for="field in draftFields"
|
v-for="field in draftFields"
|
||||||
:key="field.key"
|
:key="field.key"
|
||||||
:schema="field"
|
:schema="field"
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
@update:model-value="draft = { ...draft, [field.key]: $event }"
|
@update:model-value="draft = { ...draft, [field.key]: $event }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-modal-footer">
|
<div class="jio-modal-footer">
|
||||||
<n-button @click="closeModal">取消</n-button>
|
<n-button @click="closeModal">取消</n-button>
|
||||||
<n-button type="primary" @click="confirmModal">确定</n-button>
|
<n-button type="primary" @click="confirmModal">确定</n-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,8 +76,8 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import FaIcon from './FaIcon.vue'
|
import FaIcon from './FaIcon.vue'
|
||||||
import AksField from './AksField.vue'
|
import JioField from './JioField.vue'
|
||||||
import AksSortHandle from './AksSortHandle.vue'
|
import JioSortHandle from './JioSortHandle.vue'
|
||||||
import { matchDependency } from './field-dependency.js'
|
import { matchDependency } from './field-dependency.js'
|
||||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||||
import { createRepeaterRow, createRepeaterRowId, repeaterRowPreview } from './repeater-rows.js'
|
import { createRepeaterRow, createRepeaterRowId, repeaterRowPreview } from './repeater-rows.js'
|
||||||
@@ -151,13 +151,13 @@ function confirmModal() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-repeater-list {
|
.jio-repeater-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-repeater-list__icon-html {
|
.jio-repeater-list__icon-html {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -165,14 +165,14 @@ function confirmModal() {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-repeater-list__icon-html :deep(svg),
|
.jio-repeater-list__icon-html :deep(svg),
|
||||||
.aksrt-repeater-list__icon-html :deep(img) {
|
.jio-repeater-list__icon-html :deep(img) {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-repeater-list__modal-fields {
|
.jio-repeater-list__modal-fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
+16
-16
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="dirty" class="aksrt-form-renderer__save-bar">
|
<div v-if="dirty" class="jio-form-renderer__save-bar">
|
||||||
<div class="aksrt-form-renderer__save-bar__inner">
|
<div class="jio-form-renderer__save-bar__inner">
|
||||||
<div class="aksrt-form-renderer__save-bar__info">
|
<div class="jio-form-renderer__save-bar__info">
|
||||||
<FaIcon icon="fa fas fa-exclamation-circle" :size="14" />
|
<FaIcon icon="fa fas fa-exclamation-circle" :size="14" />
|
||||||
<span>您有未保存的更改</span>
|
<span>您有未保存的更改</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-form-renderer__save-bar__actions">
|
<div class="jio-form-renderer__save-bar__actions">
|
||||||
<n-button size="small" :disabled="saving" @click="emit('reload')">放弃更改</n-button>
|
<n-button size="small" :disabled="saving" @click="emit('reload')">放弃更改</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* AksSaveBar — the admin-wide sticky save bar (shown only when dirty).
|
* JioSaveBar — the admin-wide sticky save bar (shown only when dirty).
|
||||||
* Shared by AksFormRenderer (schema pages) and custom settings pages.
|
* Shared by JioFormRenderer (schema pages) and custom settings pages.
|
||||||
*/
|
*/
|
||||||
import FaIcon from './FaIcon.vue'
|
import FaIcon from './FaIcon.vue'
|
||||||
|
|
||||||
@@ -37,19 +37,19 @@ const emit = defineEmits(['save', 'reload'])
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-form-renderer__save-bar {
|
.jio-form-renderer__save-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
background: var(--aksrt-color-surface);
|
background: var(--jio-color-surface);
|
||||||
border-top: 1px solid var(--aksrt-color-border);
|
border-top: 1px solid var(--jio-color-border);
|
||||||
box-shadow: var(--aksrt-shadow-floating);
|
box-shadow: var(--jio-shadow-floating);
|
||||||
animation: slideUp 0.2s ease;
|
animation: slideUp 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__save-bar__inner {
|
.jio-form-renderer__save-bar__inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -59,7 +59,7 @@ const emit = defineEmits(['save', 'reload'])
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__save-bar__info {
|
.jio-form-renderer__save-bar__info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -67,7 +67,7 @@ const emit = defineEmits(['save', 'reload'])
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__save-bar__actions {
|
.jio-form-renderer__save-bar__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
@@ -84,17 +84,17 @@ const emit = defineEmits(['save', 'reload'])
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.aksrt-form-renderer__save-bar__inner {
|
.jio-form-renderer__save-bar__inner {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__save-bar__actions {
|
.jio-form-renderer__save-bar__actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-renderer__save-bar__actions .n-button {
|
.jio-form-renderer__save-bar__actions .n-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-5
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="aksrt-secret-clear">
|
<div v-if="visible" class="jio-secret-clear">
|
||||||
<n-button size="small" secondary :type="marked ? 'warning' : 'error'" @click="$emit('toggle')">
|
<n-button size="small" secondary :type="marked ? 'warning' : 'error'" @click="$emit('toggle')">
|
||||||
{{ marked ? '取消清除' : '清除已配置密钥' }}
|
{{ marked ? '取消清除' : '清除已配置密钥' }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<span v-if="marked" class="aksrt-secret-clear__hint">保存后将删除此密钥</span>
|
<span v-if="marked" class="jio-secret-clear__hint">保存后将删除此密钥</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -22,15 +22,15 @@ defineEmits(['toggle'])
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-secret-clear {
|
.jio-secret-clear {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-secret-clear__hint {
|
.jio-secret-clear__hint {
|
||||||
color: var(--aksrt-color-warning, #d97706);
|
color: var(--jio-color-warning, #d97706);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+8
-8
@@ -4,14 +4,14 @@
|
|||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
:item-key="valueKey"
|
:item-key="valueKey"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
class="aksrt-sortable aksrt-sortable--compact aksrt-selected-order"
|
class="jio-sortable jio-sortable--compact jio-selected-order"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head">
|
<div class="jio-sortable__head">
|
||||||
<AksSortHandle />
|
<JioSortHandle />
|
||||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||||
<span class="aksrt-sortable__title">{{ labelOf(element) }}</span>
|
<span class="jio-sortable__title">{{ labelOf(element) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
/** 多选 + sortable 的已选值排序列表(select / async_select 下方)。 */
|
/** 多选 + sortable 的已选值排序列表(select / async_select 下方)。 */
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import AksSortHandle from './AksSortHandle.vue'
|
import JioSortHandle from './JioSortHandle.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -47,7 +47,7 @@ function labelOf(value) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-selected-order {
|
.jio-selected-order {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button type="button" class="aksrt-sortable__handle" :title="label" :aria-label="label">
|
<button type="button" class="jio-sortable__handle" :title="label" :aria-label="label">
|
||||||
<i class="fa fas fa-grip-vertical" aria-hidden="true"></i>
|
<i class="fa fas fa-grip-vertical" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
+24
-24
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-sorter">
|
<div class="jio-sorter">
|
||||||
<div
|
<div
|
||||||
v-for="column in COLUMNS"
|
v-for="column in COLUMNS"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
class="aksrt-sorter__column"
|
class="jio-sorter__column"
|
||||||
:class="`aksrt-sorter__column--${column.key}`"
|
:class="`jio-sorter__column--${column.key}`"
|
||||||
>
|
>
|
||||||
<div class="aksrt-sorter__title">
|
<div class="jio-sorter__title">
|
||||||
<span>{{ column.title }}</span>
|
<span>{{ column.title }}</span>
|
||||||
<n-tag size="small" :type="column.tagType" :bordered="false">{{ columns[column.key].length }}</n-tag>
|
<n-tag size="small" :type="column.tagType" :bordered="false">{{ columns[column.key].length }}</n-tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,21 +14,21 @@
|
|||||||
v-model="columns[column.key]"
|
v-model="columns[column.key]"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
item-key="value"
|
item-key="value"
|
||||||
group="aksrt-sorter"
|
group="jio-sorter"
|
||||||
class="aksrt-sortable aksrt-sortable--compact aksrt-sorter__list"
|
class="jio-sortable jio-sortable--compact jio-sorter__list"
|
||||||
@change="commit"
|
@change="commit"
|
||||||
>
|
>
|
||||||
<template #item="{ element }">
|
<template #item="{ element }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head">
|
<div class="jio-sortable__head">
|
||||||
<AksSortHandle />
|
<JioSortHandle />
|
||||||
<i v-if="element.icon" :class="element.icon" class="aksrt-sorter__icon" aria-hidden="true"></i>
|
<i v-if="element.icon" :class="element.icon" class="jio-sorter__icon" aria-hidden="true"></i>
|
||||||
<span class="aksrt-sortable__title">{{ element.label }}</span>
|
<span class="jio-sortable__title">{{ element.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<div v-if="columns[column.key].length === 0" class="aksrt-sortable__empty aksrt-sorter__empty">
|
<div v-if="columns[column.key].length === 0" class="jio-sortable__empty jio-sorter__empty">
|
||||||
{{ column.empty }}
|
{{ column.empty }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
/** 启用 / 未启用两列拖拽排序器;样式见 styles/sortable-list.css。 */
|
/** 启用 / 未启用两列拖拽排序器;样式见 styles/sortable-list.css。 */
|
||||||
import { reactive, watch } from 'vue'
|
import { reactive, watch } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import AksSortHandle from './AksSortHandle.vue'
|
import JioSortHandle from './JioSortHandle.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||||
|
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
@@ -89,37 +89,37 @@ watch(() => [props.modelValue, props.options], syncColumns, { immediate: true, d
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-sorter {
|
.jio-sorter {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-sorter__column {
|
.jio-sorter__column {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 210px;
|
min-height: 210px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--aksrt-color-canvas);
|
background: var(--jio-color-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-sorter__title {
|
.jio-sorter__title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空列也要留出可放置区域,拖拽目标不能塌成 0 高度。 */
|
/* 空列也要留出可放置区域,拖拽目标不能塌成 0 高度。 */
|
||||||
.aksrt-sorter__list {
|
.jio-sorter__list {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-sorter__empty {
|
.jio-sorter__empty {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
@@ -128,15 +128,15 @@ watch(() => [props.modelValue, props.options], syncColumns, { immediate: true, d
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-sorter__icon {
|
.jio-sorter__icon {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.aksrt-sorter {
|
.jio-sorter {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* 所有后台 vuedraggable 列表共用的 Sortable 选项(v-bind 到 <draggable>)。
|
* 所有后台 vuedraggable 列表共用的 Sortable 选项(v-bind 到 <draggable>)。
|
||||||
* 配套样式见 src/styles/sortable-list.css,拖拽手柄统一用 AksSortHandle。
|
* 配套样式见 src/styles/sortable-list.css,拖拽手柄统一用 JioSortHandle。
|
||||||
*/
|
*/
|
||||||
const prefersReducedMotion = typeof window !== 'undefined'
|
const prefersReducedMotion = typeof window !== 'undefined'
|
||||||
&& typeof window.matchMedia === 'function'
|
&& typeof window.matchMedia === 'function'
|
||||||
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
|
||||||
export const SORTABLE_LIST_ATTRS = Object.freeze({
|
export const SORTABLE_LIST_ATTRS = Object.freeze({
|
||||||
handle: '.aksrt-sortable__handle',
|
handle: '.jio-sortable__handle',
|
||||||
ghostClass: 'aksrt-sortable__ghost',
|
ghostClass: 'jio-sortable__ghost',
|
||||||
chosenClass: 'aksrt-sortable__chosen',
|
chosenClass: 'jio-sortable__chosen',
|
||||||
dragClass: 'aksrt-sortable__drag',
|
dragClass: 'jio-sortable__drag',
|
||||||
animation: prefersReducedMotion ? 0 : 180,
|
animation: prefersReducedMotion ? 0 : 180,
|
||||||
})
|
})
|
||||||
@@ -2,7 +2,7 @@ import { onMounted, ref } from 'vue'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AksField 的远程选项加载:async_select / taxonomy_select / user_select / page_select。
|
* JioField 的远程选项加载:async_select / taxonomy_select / user_select / page_select。
|
||||||
* 仅按 schema.type 在挂载时拉取对应数据源。
|
* 仅按 schema.type 在挂载时拉取对应数据源。
|
||||||
*/
|
*/
|
||||||
export function useFieldRemoteOptions(props) {
|
export function useFieldRemoteOptions(props) {
|
||||||
@@ -47,7 +47,7 @@ export function useFieldRemoteOptions(props) {
|
|||||||
value: t.term_id,
|
value: t.term_id,
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[AksField] taxonomy load failed:', e)
|
console.warn('[JioField] taxonomy load failed:', e)
|
||||||
taxonomyOptions.value = []
|
taxonomyOptions.value = []
|
||||||
} finally {
|
} finally {
|
||||||
taxonomyLoading.value = false
|
taxonomyLoading.value = false
|
||||||
@@ -64,7 +64,7 @@ export function useFieldRemoteOptions(props) {
|
|||||||
value: u.id,
|
value: u.id,
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[AksField] user load failed:', e)
|
console.warn('[JioField] user load failed:', e)
|
||||||
userOptions.value = []
|
userOptions.value = []
|
||||||
} finally {
|
} finally {
|
||||||
userLoading.value = false
|
userLoading.value = false
|
||||||
@@ -84,7 +84,7 @@ export function useFieldRemoteOptions(props) {
|
|||||||
const items = res?.data || res || []
|
const items = res?.data || res || []
|
||||||
pageOptions.value = Array.isArray(items) ? items : []
|
pageOptions.value = Array.isArray(items) ? items : []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[AksField] page load failed:', e)
|
console.warn('[JioField] page load failed:', e)
|
||||||
pageOptions.value = []
|
pageOptions.value = []
|
||||||
} finally {
|
} finally {
|
||||||
pageLoading.value = false
|
pageLoading.value = false
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { renderFaIcon, schemaPage } from './helpers'
|
||||||
|
|
||||||
|
export const forumSection = {
|
||||||
|
key: 'forum',
|
||||||
|
label: '社区论坛',
|
||||||
|
icon: renderFaIcon('fa fas fa-comments'),
|
||||||
|
route: '/forum',
|
||||||
|
children: [
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-general',
|
||||||
|
name: 'Forum_general',
|
||||||
|
label: '全局设置',
|
||||||
|
title: '全局设置',
|
||||||
|
route: '/forum/general',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'general',
|
||||||
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-home',
|
||||||
|
name: 'Forum_home',
|
||||||
|
label: '首页与列表',
|
||||||
|
title: '首页与列表',
|
||||||
|
route: '/forum/home',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'home',
|
||||||
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-publishing',
|
||||||
|
name: 'Forum_publishing',
|
||||||
|
label: '发布与审核',
|
||||||
|
title: '发布与审核',
|
||||||
|
route: '/forum/publishing',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'publishing',
|
||||||
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-access',
|
||||||
|
name: 'Forum_access',
|
||||||
|
label: '权限与版主',
|
||||||
|
title: '权限与版主',
|
||||||
|
route: '/forum/access',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'access',
|
||||||
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-replies',
|
||||||
|
name: 'Forum_replies',
|
||||||
|
label: '回复与互动',
|
||||||
|
title: '回复与互动',
|
||||||
|
route: '/forum/replies',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'replies',
|
||||||
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-payment',
|
||||||
|
name: 'Forum_payment',
|
||||||
|
label: '付费与积分',
|
||||||
|
title: '付费与积分',
|
||||||
|
route: '/forum/payment',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'payment',
|
||||||
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'forum-urls',
|
||||||
|
name: 'Forum_urls',
|
||||||
|
label: '链接设置',
|
||||||
|
title: '链接设置',
|
||||||
|
route: '/forum/urls',
|
||||||
|
canonicalDomain: 'forum',
|
||||||
|
section: 'urls',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { extensionsSection } from './extensions'
|
|||||||
import { themeInfoSection } from './theme-info'
|
import { themeInfoSection } from './theme-info'
|
||||||
import { docsSection } from './docs'
|
import { docsSection } from './docs'
|
||||||
import { toolsSection } from './tools'
|
import { toolsSection } from './tools'
|
||||||
|
import { forumSection } from './forum'
|
||||||
|
|
||||||
const SECTION_DEFAULT_CHILDREN = {
|
const SECTION_DEFAULT_CHILDREN = {
|
||||||
global: 'global-common',
|
global: 'global-common',
|
||||||
@@ -18,6 +19,7 @@ const SECTION_DEFAULT_CHILDREN = {
|
|||||||
payment: 'payment-knowledge',
|
payment: 'payment-knowledge',
|
||||||
extensions: 'extensions-system',
|
extensions: 'extensions-system',
|
||||||
tools: 'tools-import-export',
|
tools: 'tools-import-export',
|
||||||
|
forum: 'forum-general',
|
||||||
'theme-info': 'theme-info-dashboard',
|
'theme-info': 'theme-info-dashboard',
|
||||||
docs: 'docs-dashboard',
|
docs: 'docs-dashboard',
|
||||||
}
|
}
|
||||||
@@ -43,6 +45,7 @@ export const ADMIN_SECTIONS = [
|
|||||||
attachSectionDefaultRoute(permissionsSection),
|
attachSectionDefaultRoute(permissionsSection),
|
||||||
attachSectionDefaultRoute(usersSection),
|
attachSectionDefaultRoute(usersSection),
|
||||||
attachSectionDefaultRoute(paymentSection),
|
attachSectionDefaultRoute(paymentSection),
|
||||||
|
attachSectionDefaultRoute(forumSection),
|
||||||
attachSectionDefaultRoute(extensionsSection),
|
attachSectionDefaultRoute(extensionsSection),
|
||||||
attachSectionDefaultRoute(toolsSection),
|
attachSectionDefaultRoute(toolsSection),
|
||||||
attachSectionDefaultRoute(themeInfoSection),
|
attachSectionDefaultRoute(themeInfoSection),
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ export const pageDisplaySection = {
|
|||||||
canonicalDomain: 'page_display',
|
canonicalDomain: 'page_display',
|
||||||
section: 'tag',
|
section: 'tag',
|
||||||
}),
|
}),
|
||||||
|
schemaPage({
|
||||||
|
key: 'page-display-topics',
|
||||||
|
name: 'PageDisplayTopics',
|
||||||
|
label: '专题页面',
|
||||||
|
route: '/page-display/topics',
|
||||||
|
title: '专题页面',
|
||||||
|
canonicalDomain: 'page_display',
|
||||||
|
section: 'topics',
|
||||||
|
}),
|
||||||
schemaPage({
|
schemaPage({
|
||||||
key: 'page-display-user-home',
|
key: 'page-display-user-home',
|
||||||
name: 'PageDisplayUserHome',
|
name: 'PageDisplayUserHome',
|
||||||
|
|||||||
+11
-11
@@ -14,35 +14,35 @@ app.use(NaiveUI)
|
|||||||
|
|
||||||
// 全局错误处理
|
// 全局错误处理
|
||||||
app.config.errorHandler = (err, vm, info) => {
|
app.config.errorHandler = (err, vm, info) => {
|
||||||
console.error('[AKSRT] 全局错误:', err, info)
|
console.error('[Jio] 全局错误:', err, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兜底:立即显示加载状态(防止白屏)
|
// 兜底:立即显示加载状态(防止白屏)
|
||||||
function showFallback(el) {
|
function showFallback(el) {
|
||||||
el.innerHTML = '<div style="padding:40px;text-align:center;color:#666;font-family:sans-serif;">'
|
el.innerHTML = '<div style="padding:40px;text-align:center;color:#666;font-family:sans-serif;">'
|
||||||
+ '<p style="font-size:18px;margin-bottom:8px;">AKSRT 正在加载...</p>'
|
+ '<p style="font-size:18px;margin-bottom:8px;">Jio 正在加载...</p>'
|
||||||
+ '<p style="font-size:13px;color:#999;">如果长时间停留在此页面,请按 F12 查看控制台错误</p>'
|
+ '<p style="font-size:13px;color:#999;">如果长时间停留在此页面,请按 F12 查看控制台错误</p>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
}
|
}
|
||||||
showFallback(document.getElementById('aksrt-admin-app') || document.body.appendChild(
|
showFallback(document.getElementById('jio-admin-app') || document.body.appendChild(
|
||||||
Object.assign(document.createElement('div'), { id: 'aksrt-admin-app' })
|
Object.assign(document.createElement('div'), { id: 'jio-admin-app' })
|
||||||
))
|
))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取或创建一个干净的挂载容器
|
* 获取或创建一个干净的挂载容器
|
||||||
* 核心问题:WordPress 后台的 jQuery/脚本会在页面加载后持续操作 DOM,
|
* 核心问题:WordPress 后台的 jQuery/脚本会在页面加载后持续操作 DOM,
|
||||||
* 导致 PHP 输出的 <div id="aksrt-admin-app"> 可能被外部修改或其兄弟节点被移除,
|
* 导致 PHP 输出的 <div id="jio-admin-app"> 可能被外部修改或其兄弟节点被移除,
|
||||||
* Vue patch 时遍历到 null 节点 → nextSibling 报错。
|
* Vue patch 时遍历到 null 节点 → nextSibling 报错。
|
||||||
*
|
*
|
||||||
* 解决方案:在挂载前一刻,动态创建一个全新的容器元素替换旧的,
|
* 解决方案:在挂载前一刻,动态创建一个全新的容器元素替换旧的,
|
||||||
* 确保该节点及其父级在 Vue 接管前不被任何外部脚本污染。
|
* 确保该节点及其父级在 Vue 接管前不被任何外部脚本污染。
|
||||||
*/
|
*/
|
||||||
function getCleanContainer() {
|
function getCleanContainer() {
|
||||||
const existing = document.getElementById('aksrt-admin-app')
|
const existing = document.getElementById('jio-admin-app')
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
// 极端情况:PHP 没有输出容器,手动创建
|
// 极端情况:PHP 没有输出容器,手动创建
|
||||||
const fresh = document.createElement('div')
|
const fresh = document.createElement('div')
|
||||||
fresh.id = 'aksrt-admin-app'
|
fresh.id = 'jio-admin-app'
|
||||||
document.body.appendChild(fresh)
|
document.body.appendChild(fresh)
|
||||||
return fresh
|
return fresh
|
||||||
}
|
}
|
||||||
@@ -53,13 +53,13 @@ function getCleanContainer() {
|
|||||||
if (!parent) {
|
if (!parent) {
|
||||||
// 旧容器已脱离文档树,重新创建
|
// 旧容器已脱离文档树,重新创建
|
||||||
const fresh = document.createElement('div')
|
const fresh = document.createElement('div')
|
||||||
fresh.id = 'aksrt-admin-app'
|
fresh.id = 'jio-admin-app'
|
||||||
document.body.appendChild(fresh)
|
document.body.appendChild(fresh)
|
||||||
return fresh
|
return fresh
|
||||||
}
|
}
|
||||||
|
|
||||||
const fresh = document.createElement('div')
|
const fresh = document.createElement('div')
|
||||||
fresh.id = 'aksrt-admin-app'
|
fresh.id = 'jio-admin-app'
|
||||||
|
|
||||||
// 将旧容器的位置信息转移给新容器(保留 fallback 内容)
|
// 将旧容器的位置信息转移给新容器(保留 fallback 内容)
|
||||||
fresh.innerHTML = existing.innerHTML || ''
|
fresh.innerHTML = existing.innerHTML || ''
|
||||||
@@ -91,12 +91,12 @@ router.isReady().then(() => {
|
|||||||
styleMountTarget.value = container
|
styleMountTarget.value = container
|
||||||
app.mount(container)
|
app.mount(container)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AKSRT] Vue 挂载失败:', err)
|
console.error('[Jio] Vue 挂载失败:', err)
|
||||||
showError(getCleanContainer(), err)
|
showError(getCleanContainer(), err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error('[AKSRT] Router 初始化失败:', err)
|
console.error('[Jio] Router 初始化失败:', err)
|
||||||
})
|
})
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<h2 class="aksrt-page-title">积分配置</h2>
|
<h2 class="jio-page-title">积分配置</h2>
|
||||||
<p class="aksrt-page-desc">
|
<p class="jio-page-desc">
|
||||||
配置积分规则与发放策略。可自定义货币名称(如”金币”、”魔石”等)。用户余额与手工调整在 WordPress 原生”用户”菜单中管理。
|
配置积分规则与发放策略。可自定义货币名称(如”金币”、”魔石”等)。用户余额与手工调整在 WordPress 原生”用户”菜单中管理。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>主开关 + 基础设置</h3></div>
|
<div class="jio-section-header"><h3>主开关 + 基础设置</h3></div>
|
||||||
<n-form :model="cfg" label-placement="left" label-width="200" class="aksrt-form">
|
<n-form :model="cfg" label-placement="left" label-width="200" class="jio-form">
|
||||||
<n-form-item label="启用积分系统">
|
<n-form-item label="启用积分系统">
|
||||||
<n-switch v-model:value="cfg.enabled" />
|
<n-switch v-model:value="cfg.enabled" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="自定义货币名称">
|
<n-form-item label="自定义货币名称">
|
||||||
<n-input v-model:value="cfg.currency_name" placeholder="积分" maxlength="20" />
|
<n-input v-model:value="cfg.currency_name" placeholder="积分" maxlength="20" />
|
||||||
<template #feedback>
|
<template #feedback>
|
||||||
<span class="aksrt-hint-text">自定义货币显示名称,如"金币"、"魔石"等。留空则使用默认名称"积分"。</span>
|
<span class="jio-hint-text">自定义货币显示名称,如"金币"、"魔石"等。留空则使用默认名称"积分"。</span>
|
||||||
</template>
|
</template>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="注册赠送积分">
|
<n-form-item label="注册赠送积分">
|
||||||
@@ -23,36 +23,9 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card title="用户近期资产记录" class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<n-space vertical :size="12">
|
<div class="jio-section-header"><h3>每日签到</h3></div>
|
||||||
<n-space wrap>
|
<n-form :model="cfg.daily_signin" label-placement="left" label-width="200" class="jio-form">
|
||||||
<n-input v-model:value="ledgerFilters.user_id" placeholder="用户 ID" clearable style="width: 130px" />
|
|
||||||
<n-select v-model:value="ledgerFilters.asset" :options="assetOptions" placeholder="资产" style="width: 130px" />
|
|
||||||
<n-input v-model:value="ledgerFilters.date_from" type="date" placeholder="开始日期" style="width: 160px" />
|
|
||||||
<n-input v-model:value="ledgerFilters.date_to" type="date" placeholder="结束日期" style="width: 160px" />
|
|
||||||
<n-button type="primary" :loading="ledgerLoading" @click="searchLedger">查询</n-button>
|
|
||||||
<n-button @click="resetLedger">重置</n-button>
|
|
||||||
</n-space>
|
|
||||||
<n-data-table
|
|
||||||
:columns="ledgerColumns"
|
|
||||||
:data="ledgerRows"
|
|
||||||
:loading="ledgerLoading"
|
|
||||||
:row-key="row => row.id"
|
|
||||||
:scroll-x="1100"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<n-pagination
|
|
||||||
v-model:page="ledgerPage"
|
|
||||||
:page-count="ledgerPageCount"
|
|
||||||
:page-size="10"
|
|
||||||
@update:page="loadLedger"
|
|
||||||
/>
|
|
||||||
</n-space>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
|
||||||
<div class="aksrt-section-header"><h3>每日签到</h3></div>
|
|
||||||
<n-form :model="cfg.daily_signin" label-placement="left" label-width="200" class="aksrt-form">
|
|
||||||
<n-form-item label="启用">
|
<n-form-item label="启用">
|
||||||
<n-switch v-model:value="cfg.daily_signin.enabled" />
|
<n-switch v-model:value="cfg.daily_signin.enabled" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -67,15 +40,15 @@
|
|||||||
:autosize="{ minRows: 2 }"
|
:autosize="{ minRows: 2 }"
|
||||||
/>
|
/>
|
||||||
<template #feedback>
|
<template #feedback>
|
||||||
<span class="aksrt-hint-text">连续 N 天时额外发放的积分。</span>
|
<span class="jio-hint-text">连续 N 天时额外发放的积分。</span>
|
||||||
</template>
|
</template>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>评论奖励</h3></div>
|
<div class="jio-section-header"><h3>评论奖励</h3></div>
|
||||||
<n-form :model="cfg.comment_reward" label-placement="left" label-width="200" class="aksrt-form">
|
<n-form :model="cfg.comment_reward" label-placement="left" label-width="200" class="jio-form">
|
||||||
<n-form-item label="启用">
|
<n-form-item label="启用">
|
||||||
<n-switch v-model:value="cfg.comment_reward.enabled" />
|
<n-switch v-model:value="cfg.comment_reward.enabled" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -94,9 +67,9 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>Ref 推广奖励</h3></div>
|
<div class="jio-section-header"><h3>Ref 推广奖励</h3></div>
|
||||||
<n-form :model="cfg.referral_reward" label-placement="left" label-width="200" class="aksrt-form">
|
<n-form :model="cfg.referral_reward" label-placement="left" label-width="200" class="jio-form">
|
||||||
<n-form-item label="启用">
|
<n-form-item label="启用">
|
||||||
<n-switch v-model:value="cfg.referral_reward.enabled" />
|
<n-switch v-model:value="cfg.referral_reward.enabled" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -118,142 +91,88 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>下载消费(参考)</h3></div>
|
<div class="jio-section-header"><h3>下载消费(参考)</h3></div>
|
||||||
<n-form :model="cfg" label-placement="left" label-width="200" class="aksrt-form">
|
<n-form :model="cfg" label-placement="left" label-width="200" class="jio-form">
|
||||||
<n-form-item :label="`默认积分价参考(${cfg.currency_name || '积分'})`">
|
<n-form-item :label="`默认积分价参考(${cfg.currency_name || '积分'})`">
|
||||||
<n-input-number v-model:value="cfg.download_default_cost" :min="0" :max="100000" />
|
<n-input-number v-model:value="cfg.download_default_cost" :min="0" :max="100000" />
|
||||||
<template #feedback>
|
<template #feedback>
|
||||||
<span class="aksrt-hint-text">实际价格以文章「付费内容设置」的积分价或现金价为准;下载解锁走收银台订单,不按次扣费。</span>
|
<span class="jio-hint-text">实际价格以文章「付费内容设置」的积分价或现金价为准;下载解锁走收银台订单,不按次扣费。</span>
|
||||||
</template>
|
</template>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<div class="aksrt-form-footer">
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="saveCfg" @reload="loadCfg" />
|
||||||
<n-button type="primary" :loading="saving" @click="saveCfg">保存配置</n-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { computed, ref, reactive, onMounted } from 'vue'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
// ---------- 配置 ----------
|
function defaultCfg() {
|
||||||
const cfg = reactive({
|
return {
|
||||||
enabled: true,
|
|
||||||
currency_name: '',
|
|
||||||
register_bonus: 10,
|
|
||||||
daily_signin: { enabled: true, base: 1, streak: { 3: 2, 7: 5, 30: 20 } },
|
|
||||||
comment_reward: { enabled: true, amount: 1, daily_cap: 5, min_length: 10, on_approved: true },
|
|
||||||
referral_reward: {
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
promoter_amount: 5,
|
currency_name: '',
|
||||||
referred_amount: 2,
|
register_bonus: 10,
|
||||||
level_points: 0,
|
daily_signin: { enabled: true, base: 1, streak: { 3: 2, 7: 5, 30: 20 } },
|
||||||
require_verify: true,
|
comment_reward: { enabled: true, amount: 1, daily_cap: 5, min_length: 10, on_approved: true },
|
||||||
ip_daily_cap: 3,
|
referral_reward: {
|
||||||
},
|
enabled: true,
|
||||||
download_default_cost: 0,
|
promoter_amount: 5,
|
||||||
})
|
referred_amount: 2,
|
||||||
const streakJson = ref('{}')
|
level_points: 0,
|
||||||
const saving = ref(false)
|
require_verify: true,
|
||||||
const ledgerLoading = ref(false)
|
ip_daily_cap: 3,
|
||||||
const ledgerRows = ref([])
|
},
|
||||||
const ledgerPage = ref(1)
|
download_default_cost: 0,
|
||||||
const ledgerPageCount = ref(1)
|
|
||||||
const ledgerFilters = reactive({
|
|
||||||
user_id: '',
|
|
||||||
asset: 'points',
|
|
||||||
date_from: '',
|
|
||||||
date_to: '',
|
|
||||||
})
|
|
||||||
const assetOptions = [
|
|
||||||
{ label: '积分', value: 'points' },
|
|
||||||
{ label: '余额', value: 'balance' },
|
|
||||||
]
|
|
||||||
const ledgerColumns = [
|
|
||||||
{ title: '类型', key: 'reason', width: 150 },
|
|
||||||
{ title: '订单号', key: 'ref_key', width: 200 },
|
|
||||||
{ title: '变动 / 变动后', key: 'delta_label', width: 190 },
|
|
||||||
{ title: '说明', key: 'remark', width: 240, ellipsis: { tooltip: true } },
|
|
||||||
{ title: '时间', key: 'created_at', width: 170 },
|
|
||||||
]
|
|
||||||
|
|
||||||
function formatAssetValue(asset, value) {
|
|
||||||
const raw = Number(value ?? 0)
|
|
||||||
if (!Number.isFinite(raw)) return asset === 'balance' ? '0.00' : '0'
|
|
||||||
if (asset !== 'balance') return String(raw)
|
|
||||||
const negative = raw < 0
|
|
||||||
const digits = String(Math.abs(Math.trunc(raw))).padStart(3, '0')
|
|
||||||
const whole = digits.slice(0, -2).replace(/^0+(?=\d)/, '')
|
|
||||||
return (negative ? '-' : '') + whole + '.' + digits.slice(-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadLedger() {
|
|
||||||
ledgerLoading.value = true
|
|
||||||
try {
|
|
||||||
if (!/^\d+$/.test(String(ledgerFilters.user_id).trim())) {
|
|
||||||
message.warning('请输入用户 ID 后查询该用户近期记录')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const params = { page: ledgerPage.value, per_page: 10 }
|
|
||||||
Object.entries(ledgerFilters).forEach(([key, value]) => {
|
|
||||||
if (value !== null && String(value).trim() !== '') params[key] = value
|
|
||||||
})
|
|
||||||
const response = await api.getCreditLog(params)
|
|
||||||
const data = response?.data || response || {}
|
|
||||||
ledgerRows.value = (data.items || []).map((row) => {
|
|
||||||
const entries = Array.isArray(row.entries) ? row.entries : []
|
|
||||||
const entry = entries[0] || {}
|
|
||||||
return {
|
|
||||||
...row,
|
|
||||||
delta_label: `${formatAssetValue(entry.asset, entry.delta)} / ${formatAssetValue(entry.asset, entry.balance_after)}`,
|
|
||||||
remark: row.remark || '—',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ledgerPageCount.value = Math.max(1, Math.ceil(Number(data.total || 0) / Number(data.per_page || 10)))
|
|
||||||
} catch (e) {
|
|
||||||
message.error(e.message || '加载资产记录失败')
|
|
||||||
} finally {
|
|
||||||
ledgerLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const cfg = reactive(defaultCfg())
|
||||||
function searchLedger() {
|
const streakJson = ref('{}')
|
||||||
ledgerPage.value = 1
|
const loading = ref(true)
|
||||||
loadLedger()
|
const saving = ref(false)
|
||||||
}
|
const initial = ref('')
|
||||||
|
const snapshot = computed(() => JSON.stringify({ cfg, streakJson: streakJson.value }))
|
||||||
function resetLedger() {
|
const dirty = computed(() => !loading.value && initial.value !== '' && snapshot.value !== initial.value)
|
||||||
Object.assign(ledgerFilters, { user_id: '', asset: 'points', date_from: '', date_to: '' })
|
|
||||||
ledgerRows.value = []
|
|
||||||
ledgerPage.value = 1
|
|
||||||
ledgerPageCount.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadCfg() {
|
async function loadCfg() {
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await api.getCreditSettings()
|
const response = await api.getCreditSettings()
|
||||||
const data = response?.data || response || {}
|
const data = response?.data || response || {}
|
||||||
Object.assign(cfg, data)
|
Object.assign(cfg, defaultCfg(), data)
|
||||||
streakJson.value = JSON.stringify(cfg.daily_signin?.streak || {}, null, 0)
|
streakJson.value = JSON.stringify(cfg.daily_signin?.streak || {}, null, 0)
|
||||||
|
initial.value = snapshot.value
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error(e.message || '加载失败')
|
message.error(e.message || '加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveCfg() {
|
async function saveCfg() {
|
||||||
|
let streak
|
||||||
|
try {
|
||||||
|
streak = JSON.parse(streakJson.value || '{}')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('连续奖励 JSON 格式错误')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!streak || typeof streak !== 'object' || Array.isArray(streak)) {
|
||||||
|
message.error('连续奖励必须是 JSON 对象')
|
||||||
|
return
|
||||||
|
}
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
let streak = {}
|
await api.saveCreditSettings({ ...cfg, daily_signin: { ...cfg.daily_signin, streak } })
|
||||||
try { streak = JSON.parse(streakJson.value || '{}') } catch (_) { /* ignore */ }
|
|
||||||
cfg.daily_signin.streak = streak
|
cfg.daily_signin.streak = streak
|
||||||
await api.saveCreditSettings(cfg)
|
initial.value = snapshot.value
|
||||||
message.success('已保存')
|
message.success('已保存')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error(e.message || '保存失败')
|
message.error(e.message || '保存失败')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-schema-page">
|
<div class="jio-schema-page">
|
||||||
<AksFormRenderer
|
<JioFormRenderer
|
||||||
:title="pageMeta.title"
|
:title="pageMeta.title"
|
||||||
:description="pageMeta.description"
|
:description="pageMeta.description"
|
||||||
:icon="pageMeta.icon"
|
:icon="pageMeta.icon"
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
v-model:value="captchaProviderTab"
|
v-model:value="captchaProviderTab"
|
||||||
type="segment"
|
type="segment"
|
||||||
animated
|
animated
|
||||||
class="aksrt-tabs aksrt-captcha-provider-tabs"
|
class="jio-tabs jio-captcha-provider-tabs"
|
||||||
>
|
>
|
||||||
<n-tab-pane
|
<n-tab-pane
|
||||||
v-for="provider in captchaProviders"
|
v-for="provider in captchaProviders"
|
||||||
@@ -48,18 +48,18 @@
|
|||||||
#field-geoip_block_countries="{ value, setValue }"
|
#field-geoip_block_countries="{ value, setValue }"
|
||||||
>
|
>
|
||||||
<n-dynamic-tags :value="Array.isArray(value) ? value : []" @update:value="setValue" />
|
<n-dynamic-tags :value="Array.isArray(value) ? value : []" @update:value="setValue" />
|
||||||
<p class="aksrt-schema-page__field-hint">使用 ISO 国家代码,如 CN / US / RU。</p>
|
<p class="jio-schema-page__field-hint">使用 ISO 国家代码,如 CN / US / RU。</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Slot for custom group content (override per page if needed) -->
|
<!-- Slot for custom group content (override per page if needed) -->
|
||||||
<template v-for="(_, name) in $slots" #[name]="slotData">
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
||||||
<slot :name="name" v-bind="slotData" />
|
<slot :name="name" v-bind="slotData" />
|
||||||
</template>
|
</template>
|
||||||
</AksFormRenderer>
|
</JioFormRenderer>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="isPermissionsLogin && !loading"
|
v-if="isPermissionsLogin && !loading"
|
||||||
class="aksrt-schema-page__tool-actions"
|
class="jio-schema-page__tool-actions"
|
||||||
>
|
>
|
||||||
<n-button
|
<n-button
|
||||||
:disabled="!formData?.enabled || !formData?.active_provider"
|
:disabled="!formData?.enabled || !formData?.active_provider"
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<n-spin />
|
<n-spin />
|
||||||
<p style="margin-top:10px;color:#999;">加载中...</p>
|
<p style="margin-top:10px;color:#999;">加载中...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="testResult && !testLoading" :style="{color: testResult.success ? 'var(--aksrt-color-primary)' : '#d03050', textAlign:'center', padding:'20px'}">
|
<div v-if="testResult && !testLoading" :style="{color: testResult.success ? 'var(--jio-color-primary)' : '#d03050', textAlign:'center', padding:'20px'}">
|
||||||
<p style="font-size:16px;margin:0;">{{ testResult.message }}</p>
|
<p style="font-size:16px;margin:0;">{{ testResult.message }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!testLoading && !testResult" id="captcha-test-box" style="min-height:150px;"></div>
|
<div v-if="!testLoading && !testResult" id="captcha-test-box" style="min-height:150px;"></div>
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
* SchemaPage — generic Schema-driven settings page.
|
* SchemaPage — generic Schema-driven settings page.
|
||||||
*
|
*
|
||||||
* Reads the schema from the Vue-side schema registry, fetches/saves settings
|
* Reads the schema from the Vue-side schema registry, fetches/saves settings
|
||||||
* via the API client, and renders using AksFormRenderer.
|
* via the API client, and renders using JioFormRenderer.
|
||||||
*
|
*
|
||||||
* Usage (in router or parent):
|
* Usage (in router or parent):
|
||||||
* <SchemaPage domain="global" section="logo" />
|
* <SchemaPage domain="global" section="logo" />
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
import { computed, watch, ref, nextTick, onBeforeUnmount } from 'vue'
|
import { computed, watch, ref, nextTick, onBeforeUnmount } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import AksFormRenderer from '@/components/form/AksFormRenderer.vue'
|
import JioFormRenderer from '@/components/form/JioFormRenderer.vue'
|
||||||
import { useSectionForm } from '@/composables/useSectionForm'
|
import { useSectionForm } from '@/composables/useSectionForm'
|
||||||
import { getSchema, normalizeSchemaDomain } from '@/schema/index.js'
|
import { getSchema, normalizeSchemaDomain } from '@/schema/index.js'
|
||||||
import api from '@/api/index.js'
|
import api from '@/api/index.js'
|
||||||
@@ -170,7 +170,7 @@ function resolveSectionFromPath(path) {
|
|||||||
|
|
||||||
/** Load schema from the Vue-side registry */
|
/** Load schema from the Vue-side registry */
|
||||||
const pageMeta = computed(() => {
|
const pageMeta = computed(() => {
|
||||||
const schema = (isEmailPage.value || resolvedDomain.value === 'page_display')
|
const schema = (isEmailPage.value || ['page_display', 'forum'].includes(resolvedDomain.value))
|
||||||
? serverSchema.value
|
? serverSchema.value
|
||||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||||
if (schema && schema.groups && schema.groups.length > 0) {
|
if (schema && schema.groups && schema.groups.length > 0) {
|
||||||
@@ -192,7 +192,7 @@ const pageMeta = computed(() => {
|
|||||||
|
|
||||||
/** Default values from schema */
|
/** Default values from schema */
|
||||||
const defaults = computed(() => {
|
const defaults = computed(() => {
|
||||||
const schema = (isEmailPage.value || resolvedDomain.value === 'page_display')
|
const schema = (isEmailPage.value || ['page_display', 'forum'].includes(resolvedDomain.value))
|
||||||
? serverSchema.value
|
? serverSchema.value
|
||||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||||
if (!schema) return {}
|
if (!schema) return {}
|
||||||
@@ -221,7 +221,7 @@ const {
|
|||||||
const payload = { ...data }
|
const payload = { ...data }
|
||||||
const clearSecrets = []
|
const clearSecrets = []
|
||||||
Object.keys(payload).forEach((key) => {
|
Object.keys(payload).forEach((key) => {
|
||||||
if (payload[key] === '__AKSRT_CLEAR_SECRET__') {
|
if (payload[key] === '__JIO_CLEAR_SECRET__') {
|
||||||
clearSecrets.push(key)
|
clearSecrets.push(key)
|
||||||
delete payload[key]
|
delete payload[key]
|
||||||
}
|
}
|
||||||
@@ -417,7 +417,7 @@ function loadProviderSDK(provider, cfg) {
|
|||||||
sitekey: cfg.site_key,
|
sitekey: cfg.site_key,
|
||||||
theme: cfg.theme || 'auto',
|
theme: cfg.theme || 'auto',
|
||||||
size: cfg.size || 'normal',
|
size: cfg.size || 'normal',
|
||||||
action: 'aksrt-admin-test',
|
action: 'jio-admin-test',
|
||||||
cdata: 'admin-captcha-test',
|
cdata: 'admin-captcha-test',
|
||||||
callback: function(token) {
|
callback: function(token) {
|
||||||
submitCaptchaTest('cloudflare', { cf_turnstile_response: token })
|
submitCaptchaTest('cloudflare', { cf_turnstile_response: token })
|
||||||
@@ -539,21 +539,21 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-schema-page {
|
.jio-schema-page {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-schema-page__tool-actions {
|
.jio-schema-page__tool-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-schema-page__field-hint {
|
.jio-schema-page__field-hint {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.captcha-test-overlay {
|
.captcha-test-overlay {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container content-page">
|
<div class="jio-page-container content-page">
|
||||||
<PageHeader title="右侧悬浮按钮" desc="显示在页面右侧的悬浮按钮,支持排序及添加自定义按钮。" />
|
<PageHeader title="右侧悬浮按钮" desc="显示在页面右侧的悬浮按钮,支持排序及添加自定义按钮。" />
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
<b>全局悬浮按钮:</b>显示在页面右侧的悬浮按钮,支持排序及添加自定义按钮
|
<b>全局悬浮按钮:</b>显示在页面右侧的悬浮按钮,支持排序及添加自定义按钮
|
||||||
</n-alert>
|
</n-alert>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
<n-form :model="form" label-placement="left" label-width="160" class="jio-form">
|
||||||
<n-form-item label="按钮样式">
|
<n-form-item label="按钮样式">
|
||||||
<n-radio-group v-model:value="form.style">
|
<n-radio-group v-model:value="form.style">
|
||||||
<n-space>
|
<n-space>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
v-model="form.items"
|
v-model="form.items"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
item-key="type"
|
item-key="type"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<FloatBtnItemPanel :model-value="element" @update:model-value="updateItem(index, $event)" />
|
<FloatBtnItemPanel :model-value="element" @update:model-value="updateItem(index, $event)" />
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
|
||||||
<AksSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="loadSettings" />
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="loadSettings" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ import { useMessage } from 'naive-ui'
|
|||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
import AksSaveBar from '@/components/form/AksSaveBar.vue'
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||||
import FloatBtnItemPanel from '@/components/floating/FloatBtnItemPanel.vue'
|
import FloatBtnItemPanel from '@/components/floating/FloatBtnItemPanel.vue'
|
||||||
import { defaultSettings, normalizeItems } from '@/components/floating/floating-defaults.js'
|
import { defaultSettings, normalizeItems } from '@/components/floating/floating-defaults.js'
|
||||||
@@ -162,13 +162,13 @@ onMounted(loadSettings)
|
|||||||
|
|
||||||
.float-settings__desc {
|
.float-settings__desc {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-settings__label {
|
.float-settings__label {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-settings__section {
|
.float-settings__section {
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container content-page">
|
<div class="jio-page-container content-page">
|
||||||
<PageHeader title="底部Tab配置" desc="自定义移动端底部导航栏的按钮,支持排序、图标配置和返回顶部功能。">
|
<PageHeader title="底部Tab配置" desc="自定义移动端底部导航栏的按钮,支持排序、图标配置和返回顶部功能。" />
|
||||||
<template #actions>
|
|
||||||
<n-button type="primary" :loading="saving" @click="handleSave">保存</n-button>
|
|
||||||
</template>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
<!-- 基础设置 -->
|
<!-- 基础设置 -->
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="aksrt-card-header-row">
|
<div class="jio-card-header-row">
|
||||||
<div class="aksrt-card-header-row__left">
|
<div class="jio-card-header-row__left">
|
||||||
<span class="header-icon"><i class="fa fas fa-cog"></i></span>
|
<span class="header-icon"><i class="fa fas fa-cog"></i></span>
|
||||||
<strong class="aksrt-card-header-row__title">基础设置</strong>
|
<strong class="jio-card-header-row__title">基础设置</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<n-form :model="form" label-placement="left" label-width="140" class="aksrt-form">
|
<n-form :model="form" label-placement="left" label-width="140" class="jio-form">
|
||||||
<n-form-item label="启用底部Tab">
|
<n-form-item label="启用底部Tab">
|
||||||
<n-switch v-model:value="form.enabled" />
|
<n-switch v-model:value="form.enabled" />
|
||||||
<span class="aksrt-hint-text" style="margin-left: 12px;">移动端底部导航栏总开关</span>
|
<span class="jio-hint-text" style="margin-left: 12px;">移动端底部导航栏总开关</span>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="向下滚动隐藏">
|
<n-form-item label="向下滚动隐藏">
|
||||||
<n-switch v-model:value="form.scroll_down_hide" />
|
<n-switch v-model:value="form.scroll_down_hide" />
|
||||||
<span class="aksrt-hint-text" style="margin-left: 12px;">页面向下滚动时隐藏,向上滚动时显示</span>
|
<span class="jio-hint-text" style="margin-left: 12px;">页面向下滚动时隐藏,向上滚动时显示</span>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="文章页专属Tab">
|
<n-form-item label="文章页专属Tab">
|
||||||
<n-select
|
<n-select
|
||||||
@@ -36,18 +32,18 @@
|
|||||||
]"
|
]"
|
||||||
style="width: 240px;"
|
style="width: 240px;"
|
||||||
/>
|
/>
|
||||||
<span class="aksrt-hint-text" style="margin-left: 12px;">文章详情页底部显示的Tab按钮类型</span>
|
<span class="jio-hint-text" style="margin-left: 12px;">文章详情页底部显示的Tab按钮类型</span>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- Tab按钮配置 -->
|
<!-- Tab按钮配置 -->
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="aksrt-card-header-row">
|
<div class="jio-card-header-row">
|
||||||
<div class="aksrt-card-header-row__left">
|
<div class="jio-card-header-row__left">
|
||||||
<span class="header-icon"><i class="fa fas fa-list-ul"></i></span>
|
<span class="header-icon"><i class="fa fas fa-list-ul"></i></span>
|
||||||
<strong class="aksrt-card-header-row__title">Tab按钮配置 ({{ form.items.length }})</strong>
|
<strong class="jio-card-header-row__title">Tab按钮配置 ({{ form.items.length }})</strong>
|
||||||
</div>
|
</div>
|
||||||
<n-button type="primary" size="small" @click="addItem">
|
<n-button type="primary" size="small" @click="addItem">
|
||||||
<template #icon><i class="fa fas fa-plus"></i></template>
|
<template #icon><i class="fa fas fa-plus"></i></template>
|
||||||
@@ -63,27 +59,27 @@
|
|||||||
v-model="form.items"
|
v-model="form.items"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
@end="resetSortOrder"
|
@end="resetSortOrder"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head">
|
<div class="jio-sortable__head">
|
||||||
<AksSortHandle />
|
<JioSortHandle />
|
||||||
<span class="aksrt-sortable__icon">
|
<span class="jio-sortable__icon">
|
||||||
<IconValue v-if="element.icon" :value="element.icon" />
|
<IconValue v-if="element.icon" :value="element.icon" />
|
||||||
</span>
|
</span>
|
||||||
<div class="aksrt-sortable__main">
|
<div class="jio-sortable__main">
|
||||||
<div class="aksrt-sortable__title-row">
|
<div class="jio-sortable__title-row">
|
||||||
<span class="aksrt-sortable__title">{{ element.text || '按钮' }}</span>
|
<span class="jio-sortable__title">{{ element.text || '按钮' }}</span>
|
||||||
<n-tag size="small" :bordered="false">{{ typeLabelMap[element.type] || element.type }}</n-tag>
|
<n-tag size="small" :bordered="false">{{ typeLabelMap[element.type] || element.type }}</n-tag>
|
||||||
<n-tag v-if="element.badge" size="small" type="error" :bordered="false">徽标 {{ element.badge }}</n-tag>
|
<n-tag v-if="element.badge" size="small" type="error" :bordered="false">徽标 {{ element.badge }}</n-tag>
|
||||||
<n-tag v-if="element.type === 'home' && element.ontop" size="small" type="success" :bordered="false">返回顶部</n-tag>
|
<n-tag v-if="element.type === 'home' && element.ontop" size="small" type="success" :bordered="false">返回顶部</n-tag>
|
||||||
<n-tag size="small" :type="element.enabled ? 'success' : 'default'" :bordered="false">{{ element.enabled ? '启用' : '禁用' }}</n-tag>
|
<n-tag size="small" :type="element.enabled ? 'success' : 'default'" :bordered="false">{{ element.enabled ? '启用' : '禁用' }}</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-sortable__desc">{{ getItemSummary(element) }}</div>
|
<div class="jio-sortable__desc">{{ getItemSummary(element) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-sortable__actions">
|
<div class="jio-sortable__actions">
|
||||||
<n-button size="tiny" quaternary type="primary" @click="editItem(index)">
|
<n-button size="tiny" quaternary type="primary" @click="editItem(index)">
|
||||||
<template #icon><i class="fa fas fa-edit" style="font-size:12px;" /></template>
|
<template #icon><i class="fa fas fa-edit" style="font-size:12px;" /></template>
|
||||||
编辑
|
编辑
|
||||||
@@ -99,22 +95,20 @@
|
|||||||
</draggable>
|
</draggable>
|
||||||
<n-empty v-else description="暂无Tab按钮,请点击上方按钮添加" />
|
<n-empty v-else description="暂无Tab按钮,请点击上方按钮添加" />
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<div class="aksrt-form-actions">
|
|
||||||
<n-button type="primary" :loading="saving" @click="handleSave">保存底部Tab配置</n-button>
|
|
||||||
</div>
|
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
|
||||||
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="loadSettings" />
|
||||||
|
|
||||||
<!-- 编辑弹窗 -->
|
<!-- 编辑弹窗 -->
|
||||||
<n-modal
|
<n-modal
|
||||||
v-model:show="showModal"
|
v-model:show="showModal"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
preset="card"
|
preset="card"
|
||||||
:title="editingIndex >= 0 ? '编辑Tab按钮' : '添加Tab按钮'"
|
:title="editingIndex >= 0 ? '编辑Tab按钮' : '添加Tab按钮'"
|
||||||
class="aksrt-modal-card"
|
class="jio-modal-card"
|
||||||
style="max-width: 520px;"
|
style="max-width: 520px;"
|
||||||
>
|
>
|
||||||
<n-form :model="editingItem" label-placement="top" class="aksrt-form">
|
<n-form :model="editingItem" label-placement="top" class="jio-form">
|
||||||
<n-form-item label="按钮类型">
|
<n-form-item label="按钮类型">
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="editingItem.type"
|
v-model:value="editingItem.type"
|
||||||
@@ -214,12 +208,12 @@
|
|||||||
v-model="editingItem.add_items"
|
v-model="editingItem.add_items"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
class="aksrt-sortable aksrt-sortable--compact"
|
class="jio-sortable jio-sortable--compact"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index: subIndex }">
|
<template #item="{ element, index: subIndex }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head add-items-editor__row">
|
<div class="jio-sortable__head add-items-editor__row">
|
||||||
<AksSortHandle />
|
<JioSortHandle />
|
||||||
<n-input v-model:value="element.label" placeholder="名称,例如:写文章" />
|
<n-input v-model:value="element.label" placeholder="名称,例如:写文章" />
|
||||||
<FaIconPicker v-model="element.icon" placeholder="选择图标" />
|
<FaIconPicker v-model="element.icon" placeholder="选择图标" />
|
||||||
<n-input v-model:value="element.url" placeholder="链接,例如:/wp-admin/post-new.php" />
|
<n-input v-model:value="element.url" placeholder="链接,例如:/wp-admin/post-new.php" />
|
||||||
@@ -236,7 +230,7 @@
|
|||||||
|
|
||||||
<n-form-item v-if="editingItem.type === 'home'" label="返回顶部功能">
|
<n-form-item v-if="editingItem.type === 'home'" label="返回顶部功能">
|
||||||
<n-switch v-model:value="editingItem.ontop" />
|
<n-switch v-model:value="editingItem.ontop" />
|
||||||
<span class="aksrt-hint-text" style="margin-left: 12px;">页面滚动后切换为返回顶部按钮</span>
|
<span class="jio-hint-text" style="margin-left: 12px;">页面滚动后切换为返回顶部按钮</span>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item label="启用状态">
|
<n-form-item label="启用状态">
|
||||||
@@ -245,7 +239,7 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="aksrt-modal-footer">
|
<div class="jio-modal-footer">
|
||||||
<n-button @click="showModal = false">取消</n-button>
|
<n-button @click="showModal = false">取消</n-button>
|
||||||
<n-button type="primary" @click="saveItem">保存</n-button>
|
<n-button type="primary" @click="saveItem">保存</n-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,9 +254,10 @@ import { NButton, NTag, useDialog, useMessage } from 'naive-ui'
|
|||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||||
import IconValue from '@/components/form/IconValue.vue'
|
import IconValue from '@/components/form/IconValue.vue'
|
||||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -280,6 +275,12 @@ const form = reactive({
|
|||||||
footer_tabbar_single: 'default',
|
footer_tabbar_single: 'default',
|
||||||
items: [],
|
items: [],
|
||||||
})
|
})
|
||||||
|
const snapshot = ref('')
|
||||||
|
const serialize = () => JSON.stringify(form)
|
||||||
|
const dirty = computed(() => !loading.value && snapshot.value !== '' && serialize() !== snapshot.value)
|
||||||
|
function markClean() {
|
||||||
|
snapshot.value = serialize()
|
||||||
|
}
|
||||||
|
|
||||||
const editingItem = reactive({
|
const editingItem = reactive({
|
||||||
id: '',
|
id: '',
|
||||||
@@ -552,6 +553,7 @@ async function loadSettings() {
|
|||||||
add_items: Array.isArray(item.add_items) ? item.add_items : [],
|
add_items: Array.isArray(item.add_items) ? item.add_items : [],
|
||||||
sort_order: item.sort_order ?? idx,
|
sort_order: item.sort_order ?? idx,
|
||||||
}))
|
}))
|
||||||
|
markClean()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[FooterTabbar] load failed:', e)
|
console.error('[FooterTabbar] load failed:', e)
|
||||||
@@ -581,6 +583,7 @@ async function handleSave() {
|
|||||||
}
|
}
|
||||||
const res = await api.saveFooterTabbarSettings(payload)
|
const res = await api.saveFooterTabbarSettings(payload)
|
||||||
if (res.code === 'success') {
|
if (res.code === 'success') {
|
||||||
|
markClean()
|
||||||
message.success(res.message || '保存成功')
|
message.success(res.message || '保存成功')
|
||||||
} else {
|
} else {
|
||||||
message.error(res.message || '保存失败')
|
message.error(res.message || '保存失败')
|
||||||
@@ -606,7 +609,7 @@ onMounted(loadSettings)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-preview-row a {
|
.icon-preview-row a {
|
||||||
color: var(--aksrt-color-primary, #18A058);
|
color: var(--jio-color-primary, #18A058);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,12 +618,12 @@ onMounted(loadSettings)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.muted-text {
|
.muted-text {
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-desc {
|
.section-desc {
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
}
|
}
|
||||||
@@ -650,7 +653,7 @@ onMounted(loadSettings)
|
|||||||
|
|
||||||
.add-items-editor__presets-label {
|
.add-items-editor__presets-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-items-editor__row {
|
.add-items-editor__row {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container content-page">
|
<div class="jio-page-container content-page">
|
||||||
<PageHeader title="通知管理" desc="管理通知内容开关、user 徽标统计口径和自动清理策略。">
|
<PageHeader title="通知管理" desc="管理通知内容开关、user 徽标统计口径和自动清理策略。">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<n-space>
|
<n-space>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
<n-card title="通知内容开关" class="aksrt-section-card">
|
<n-card title="通知内容开关" class="jio-section-card">
|
||||||
<n-form :model="settings" label-placement="left" label-width="180">
|
<n-form :model="settings" label-placement="left" label-width="180">
|
||||||
<n-form-item label="评论回复通知">
|
<n-form-item label="评论回复通知">
|
||||||
<n-switch v-model:value="settings.enabled_types.comment_reply" />
|
<n-switch v-model:value="settings.enabled_types.comment_reply" />
|
||||||
@@ -35,21 +35,21 @@
|
|||||||
|
|
||||||
<n-grid :cols="2" :x-gap="16" responsive="screen" item-responsive style="margin-top: 16px;">
|
<n-grid :cols="2" :x-gap="16" responsive="screen" item-responsive style="margin-top: 16px;">
|
||||||
<n-grid-item span="24 l:10">
|
<n-grid-item span="24 l:10">
|
||||||
<n-card title="user 徽标规则" class="aksrt-section-card">
|
<n-card title="user 徽标规则" class="jio-section-card">
|
||||||
<n-form :model="settings" label-placement="left" label-width="180">
|
<n-form :model="settings" label-placement="left" label-width="180">
|
||||||
<n-form-item label="启用 user 动态徽标">
|
<n-form-item label="启用 user 动态徽标">
|
||||||
<n-switch v-model:value="settings.user_badge_enabled" />
|
<n-switch v-model:value="settings.user_badge_enabled" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="徽标包含状态提醒">
|
<n-form-item label="徽标包含状态提醒">
|
||||||
<n-switch v-model:value="settings.badge_include_stateful" />
|
<n-switch v-model:value="settings.badge_include_stateful" />
|
||||||
<span class="aksrt-hint-text" style="margin-left: 12px;">未读持久化通知之外,叠加邮箱待验证、待审核评论等即时提醒</span>
|
<span class="jio-hint-text" style="margin-left: 12px;">未读持久化通知之外,叠加邮箱待验证、待审核评论等即时提醒</span>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
|
|
||||||
<n-grid-item span="24 l:14">
|
<n-grid-item span="24 l:14">
|
||||||
<n-card title="通知清理策略" class="aksrt-section-card">
|
<n-card title="通知清理策略" class="jio-section-card">
|
||||||
<n-form :model="settings" label-placement="left" label-width="180">
|
<n-form :model="settings" label-placement="left" label-width="180">
|
||||||
<n-form-item label="已读通知保留天数">
|
<n-form-item label="已读通知保留天数">
|
||||||
<n-input-number v-model:value="settings.read_retention_days" :min="1" :max="365" style="width: 180px;" />
|
<n-input-number v-model:value="settings.read_retention_days" :min="1" :max="365" style="width: 180px;" />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader title="文档中心" desc="主题使用文档、开发指南、迁移说明和更新日志。" />
|
<PageHeader title="文档中心" desc="主题使用文档、开发指南、迁移说明和更新日志。" />
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
<!-- Quick Actions -->
|
||||||
<n-card title="常用操作" size="small" class="aksrt-docs__section">
|
<n-card title="常用操作" size="small" class="jio-docs__section">
|
||||||
<n-grid :cols="2" :x-gap="12" :y-gap="12">
|
<n-grid :cols="2" :x-gap="12" :y-gap="12">
|
||||||
<n-grid-item>
|
<n-grid-item>
|
||||||
<div class="aksrt-docs__action">
|
<div class="jio-docs__action">
|
||||||
<FaIcon icon="fa fas fa-paint-brush" :size="16" />
|
<FaIcon icon="fa fas fa-paint-brush" :size="16" />
|
||||||
<div>
|
<div>
|
||||||
<strong>全局设置</strong>
|
<strong>全局设置</strong>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
<n-grid-item>
|
<n-grid-item>
|
||||||
<div class="aksrt-docs__action">
|
<div class="jio-docs__action">
|
||||||
<FaIcon icon="fa fas fa-home" :size="16" />
|
<FaIcon icon="fa fas fa-home" :size="16" />
|
||||||
<div>
|
<div>
|
||||||
<strong>首页配置</strong>
|
<strong>首页配置</strong>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
<n-grid-item>
|
<n-grid-item>
|
||||||
<div class="aksrt-docs__action">
|
<div class="jio-docs__action">
|
||||||
<FaIcon icon="fa fas fa-users" :size="16" />
|
<FaIcon icon="fa fas fa-users" :size="16" />
|
||||||
<div>
|
<div>
|
||||||
<strong>用户管理</strong>
|
<strong>用户管理</strong>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
<n-grid-item>
|
<n-grid-item>
|
||||||
<div class="aksrt-docs__action">
|
<div class="jio-docs__action">
|
||||||
<FaIcon icon="fa fas fa-wrench" :size="16" />
|
<FaIcon icon="fa fas fa-wrench" :size="16" />
|
||||||
<div>
|
<div>
|
||||||
<strong>工具</strong>
|
<strong>工具</strong>
|
||||||
@@ -49,30 +49,30 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- Reference Docs -->
|
<!-- Reference Docs -->
|
||||||
<div class="aksrt-docs__grid">
|
<div class="jio-docs__grid">
|
||||||
<n-card
|
<n-card
|
||||||
v-for="doc in docs"
|
v-for="doc in docs"
|
||||||
:key="doc.key"
|
:key="doc.key"
|
||||||
class="aksrt-docs__card"
|
class="jio-docs__card"
|
||||||
size="small"
|
size="small"
|
||||||
hoverable
|
hoverable
|
||||||
@click="openDoc(doc)"
|
@click="openDoc(doc)"
|
||||||
>
|
>
|
||||||
<div class="aksrt-docs__card-inner">
|
<div class="jio-docs__card-inner">
|
||||||
<div class="aksrt-docs__card-icon" :style="{ background: doc.color }">
|
<div class="jio-docs__card-icon" :style="{ background: doc.color }">
|
||||||
<FaIcon :icon="doc.icon" :size="20" color="#fff" />
|
<FaIcon :icon="doc.icon" :size="20" color="#fff" />
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-docs__card-body">
|
<div class="jio-docs__card-body">
|
||||||
<h4 class="aksrt-docs__card-title">{{ doc.title }}</h4>
|
<h4 class="jio-docs__card-title">{{ doc.title }}</h4>
|
||||||
<p class="aksrt-docs__card-desc">{{ doc.description }}</p>
|
<p class="jio-docs__card-desc">{{ doc.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
<FaIcon icon="fa fas fa-external-link-alt" :size="12" class="aksrt-docs__card-arrow" />
|
<FaIcon icon="fa fas fa-external-link-alt" :size="12" class="jio-docs__card-arrow" />
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Version Info -->
|
<!-- Version Info -->
|
||||||
<n-card title="当前版本" size="small" class="aksrt-docs__section">
|
<n-card title="当前版本" size="small" class="jio-docs__section">
|
||||||
<n-descriptions :column="2" bordered label-placement="left" size="small">
|
<n-descriptions :column="2" bordered label-placement="left" size="small">
|
||||||
<n-descriptions-item label="主题版本">
|
<n-descriptions-item label="主题版本">
|
||||||
<n-tag type="success" size="small">{{ themeVersion }}</n-tag>
|
<n-tag type="success" size="small">{{ themeVersion }}</n-tag>
|
||||||
@@ -91,12 +91,12 @@ import PageHeader from '@/components/PageHeader.vue'
|
|||||||
import FaIcon from '@/components/form/FaIcon.vue'
|
import FaIcon from '@/components/form/FaIcon.vue'
|
||||||
import api from '@/api/index.js'
|
import api from '@/api/index.js'
|
||||||
|
|
||||||
const themeVersion = ref(window.aksrtAdminData?.themeVersion || '3.4.0-dev')
|
const themeVersion = ref(window.jioAdminData?.themeVersion || '3.4.0-dev')
|
||||||
const dbSchemaVersion = ref('—')
|
const dbSchemaVersion = ref('—')
|
||||||
|
|
||||||
const docs = ref([
|
const docs = ref([
|
||||||
{ key: 'admin-ia-map', title: '后台 IA 蓝图', description: '完整的后台信息架构映射,12 个顶级分区及子页面清单。', icon: 'fa fas fa-sitemap', color: 'var(--aksrt-color-primary)', file: 'admin-ia-map.md' },
|
{ key: 'admin-ia-map', title: '后台 IA 蓝图', description: '完整的后台信息架构映射,12 个顶级分区及子页面清单。', icon: 'fa fas fa-sitemap', color: 'var(--jio-color-primary)', file: 'admin-ia-map.md' },
|
||||||
{ key: 'legacy-gap-matrix', title: '对标差异矩阵', description: 'AKSRT 当前 → 原主题参考 → AKSRT 目标三层对照表。', icon: 'fa fas fa-columns', color: '#722ed1', file: 'legacy-gap-matrix.md' },
|
{ key: 'legacy-gap-matrix', title: '对标差异矩阵', description: 'Jio 当前 → 原主题参考 → Jio 目标三层对照表。', icon: 'fa fas fa-columns', color: '#722ed1', file: 'legacy-gap-matrix.md' },
|
||||||
{ key: 'frontend-page-matrix', title: '前端页面矩阵', description: '前端模板、CSS、JS 资产映射与覆盖范围。', icon: 'fa fas fa-desktop', color: '#13c2c2', file: 'frontend-page-matrix.md' },
|
{ key: 'frontend-page-matrix', title: '前端页面矩阵', description: '前端模板、CSS、JS 资产映射与覆盖范围。', icon: 'fa fas fa-desktop', color: '#13c2c2', file: 'frontend-page-matrix.md' },
|
||||||
{ key: 'manual-acceptance-baseline', title: '验收基线', description: '每个 Phase 的人工验收标准和检查清单。', icon: 'fa fas fa-clipboard', color: '#52c41a', file: 'manual-acceptance-baseline.md' },
|
{ key: 'manual-acceptance-baseline', title: '验收基线', description: '每个 Phase 的人工验收标准和检查清单。', icon: 'fa fas fa-clipboard', color: '#52c41a', file: 'manual-acceptance-baseline.md' },
|
||||||
{ key: 'feature-domain-map', title: '功能域地图', description: '业务域划分、依赖关系和模块边界说明。', icon: 'fa fas fa-map', color: '#fa8c16', file: 'feature-domain-map.md' },
|
{ key: 'feature-domain-map', title: '功能域地图', description: '业务域划分、依赖关系和模块边界说明。', icon: 'fa fas fa-map', color: '#fa8c16', file: 'feature-domain-map.md' },
|
||||||
@@ -117,35 +117,35 @@ onMounted(async () => {
|
|||||||
|
|
||||||
function openDoc(doc) {
|
function openDoc(doc) {
|
||||||
// Link to local docs directory (admin can open in new tab)
|
// Link to local docs directory (admin can open in new tab)
|
||||||
const base = window.aksrtAdminData?.siteUrl || window.location.origin
|
const base = window.jioAdminData?.siteUrl || window.location.origin
|
||||||
window.open(`${base}/wp-content/themes/AKSRT-theme/docs/${doc.file}`, '_blank')
|
window.open(`${base}/wp-content/themes/Jio/docs/${doc.file}`, '_blank')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-docs__grid {
|
.jio-docs__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card {
|
.jio-docs__card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: box-shadow 0.2s;
|
transition: box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card:hover {
|
.jio-docs__card:hover {
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card-inner {
|
.jio-docs__card-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card-icon {
|
.jio-docs__card-icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -155,38 +155,38 @@ function openDoc(doc) {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card-body {
|
.jio-docs__card-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card-title {
|
.jio-docs__card-title {
|
||||||
margin: 0 0 2px;
|
margin: 0 0 2px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card-desc {
|
.jio-docs__card-desc {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__card-arrow {
|
.jio-docs__card-arrow {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: #c0c4cc;
|
color: #c0c4cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__section {
|
.jio-docs__section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__action {
|
.jio-docs__action {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -195,31 +195,31 @@ function openDoc(doc) {
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__action i {
|
.jio-docs__action i {
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__action strong {
|
.jio-docs__action strong {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--aksrt-color-text);
|
color: var(--jio-color-text);
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__action p {
|
.jio-docs__action p {
|
||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__action a {
|
.jio-docs__action a {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-primary);
|
color: var(--jio-color-primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-docs__action a:hover {
|
.jio-docs__action a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader :title="title" :desc="desc">
|
<PageHeader :title="title" :desc="desc">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<n-space v-if="links.length">
|
<n-space v-if="links.length">
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-card v-if="note" class="aksrt-section-card">
|
<n-card v-if="note" class="jio-section-card">
|
||||||
<n-alert type="info" :show-icon="false">
|
<n-alert type="info" :show-icon="false">
|
||||||
{{ note }}
|
{{ note }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="基本权限 / 能力矩阵"
|
title="基本权限 / 能力矩阵"
|
||||||
desc="统一管理用户治理、投稿和评论的角色能力矩阵。"
|
desc="统一管理用户治理、投稿和评论的角色能力矩阵。"
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 240px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 240px;">
|
||||||
<n-card class="aksrt-section-card" v-if="summary.cap_count">
|
<n-card class="jio-section-card" v-if="summary.cap_count">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>矩阵概览</h3>
|
<h3>矩阵概览</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-space>
|
<n-space>
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card v-for="group in groups" :key="group.key" class="aksrt-section-card">
|
<n-card v-for="group in groups" :key="group.key" class="jio-section-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="aksrt-cap-matrix__header">
|
<div class="jio-cap-matrix__header">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="aksrt-cap-matrix__title">{{ group.title }}</h3>
|
<h3 class="jio-cap-matrix__title">{{ group.title }}</h3>
|
||||||
<p class="aksrt-cap-matrix__desc">共 {{ group.items.length }} 项能力</p>
|
<p class="jio-cap-matrix__desc">共 {{ group.items.length }} 项能力</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,20 +47,19 @@
|
|||||||
:scroll-x="1200"
|
:scroll-x="1200"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<div class="aksrt-form-actions">
|
|
||||||
<n-button type="primary" :loading="saving" @click="handleSave">保存能力矩阵</n-button>
|
|
||||||
</div>
|
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
|
||||||
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="load" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { h, onMounted, reactive, ref } from 'vue'
|
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||||
import { NInputNumber, NSwitch, NTag } from 'naive-ui'
|
import { NInputNumber, NSwitch, NTag } from 'naive-ui'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -74,6 +73,7 @@ const summary = reactive({
|
|||||||
})
|
})
|
||||||
const userCaps = reactive({})
|
const userCaps = reactive({})
|
||||||
const defaultsSnapshot = ref({})
|
const defaultsSnapshot = ref({})
|
||||||
|
const dirty = computed(() => !loading.value && JSON.stringify(userCaps) !== JSON.stringify(defaultsSnapshot.value))
|
||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ function buildColumns(group) {
|
|||||||
key: 'label',
|
key: 'label',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 280,
|
width: 280,
|
||||||
render: (row) => h('div', { class: 'aksrt-cap-matrix__cap' }, [
|
render: (row) => h('div', { class: 'jio-cap-matrix__cap' }, [
|
||||||
h('strong', row.label),
|
h('strong', row.label),
|
||||||
h('code', row.cap_id),
|
h('code', row.cap_id),
|
||||||
]),
|
]),
|
||||||
@@ -148,7 +148,7 @@ function buildColumns(group) {
|
|||||||
function renderCapCell(row, col) {
|
function renderCapCell(row, col) {
|
||||||
const hidden = !(row.visible_columns || []).includes(col)
|
const hidden = !(row.visible_columns || []).includes(col)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return h('span', { class: 'aksrt-cap-matrix__empty' }, '—')
|
return h('span', { class: 'jio-cap-matrix__empty' }, '—')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userCaps[row.cap_id]) {
|
if (!userCaps[row.cap_id]) {
|
||||||
@@ -186,18 +186,11 @@ function resetDefaults() {
|
|||||||
message.info('已恢复默认矩阵')
|
message.info('已恢复默认矩阵')
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandAll() {
|
|
||||||
message.info('当前矩阵为平铺展示,无需展开')
|
|
||||||
}
|
|
||||||
|
|
||||||
function collapseAll() {
|
|
||||||
message.info('当前矩阵为平铺展示,无需折叠')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await api.saveCapabilities(JSON.parse(JSON.stringify(userCaps)))
|
const res = await api.saveCapabilities(JSON.parse(JSON.stringify(userCaps)))
|
||||||
|
if (res?.data?.user_caps) replaceReactive(userCaps, res.data.user_caps)
|
||||||
defaultsSnapshot.value = JSON.parse(JSON.stringify(userCaps))
|
defaultsSnapshot.value = JSON.parse(JSON.stringify(userCaps))
|
||||||
message.success('能力矩阵已保存')
|
message.success('能力矩阵已保存')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -209,42 +202,36 @@ async function handleSave() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-cap-matrix__header {
|
.jio-cap-matrix__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-cap-matrix__title {
|
.jio-cap-matrix__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-cap-matrix__desc {
|
.jio-cap-matrix__desc {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-cap-matrix__cap {
|
.jio-cap-matrix__cap {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-cap-matrix__cap code {
|
.jio-cap-matrix__cap code {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #667085;
|
color: #667085;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-cap-matrix__empty {
|
.jio-cap-matrix__empty {
|
||||||
color: #c0c4cc;
|
color: #c0c4cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-form-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aks-native-ops-redirect">
|
<div class="jio-native-ops-redirect">
|
||||||
<h2>{{ title }}</h2>
|
<h2>{{ title }}</h2>
|
||||||
<p class="desc">
|
<p class="desc">
|
||||||
此运营功能已迁移到 WordPress 原生后台侧栏(对齐 legacy)。正在跳转…
|
此运营功能已迁移到 WordPress 原生后台侧栏(对齐 legacy)。正在跳转…
|
||||||
@@ -21,7 +21,7 @@ const title = computed(() => route.meta?.title || '运营功能已迁移')
|
|||||||
const nativeUrl = computed(() => {
|
const nativeUrl = computed(() => {
|
||||||
const path = route.meta?.nativeAdminPath || ''
|
const path = route.meta?.nativeAdminPath || ''
|
||||||
if (!path) return ''
|
if (!path) return ''
|
||||||
const base = (window.aksrtAdminData?.adminUrl || '/wp-admin/').replace(/\/?$/, '/')
|
const base = (window.jioAdminData?.adminUrl || '/wp-admin/').replace(/\/?$/, '/')
|
||||||
return base + path.replace(/^\//, '')
|
return base + path.replace(/^\//, '')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -33,13 +33,13 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aks-native-ops-redirect {
|
.jio-native-ops-redirect {
|
||||||
max-width: 640px;
|
max-width: 640px;
|
||||||
margin: 48px auto;
|
margin: 48px auto;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background: var(--aks-surface, #fff);
|
background: var(--jio-surface, #fff);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--aks-border, #e5e7eb);
|
border: 1px solid var(--jio-border, #e5e7eb);
|
||||||
}
|
}
|
||||||
.desc {
|
.desc {
|
||||||
color: #646970;
|
color: #646970;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="主题授权"
|
title="主题授权"
|
||||||
desc="当前版本按产品决策不实现在线授权/撤销授权,仅保留主题信息与更新能力。"
|
desc="当前版本按产品决策不实现在线授权/撤销授权,仅保留主题信息与更新能力。"
|
||||||
@@ -12,14 +12,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<n-alert type="info" title="按设计省略" :show-icon="true">
|
<n-alert type="info" title="按设计省略" :show-icon="true">
|
||||||
本主题当前不实现 原主题风格的授权码校验、域名绑定、撤销授权、本地免授权等机制。
|
本主题当前不实现 原主题风格的授权码校验、域名绑定、撤销授权、本地免授权等机制。
|
||||||
`AKSRT_Update_Checker` 仅负责匿名版本检测与更新提示,不与授权系统耦合。
|
`JIO_Update_Checker` 仅负责匿名版本检测与更新提示,不与授权系统耦合。
|
||||||
</n-alert>
|
</n-alert>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card" title="当前策略" size="small">
|
<n-card class="jio-section-card" title="当前策略" size="small">
|
||||||
<n-list bordered>
|
<n-list bordered>
|
||||||
<n-list-item>不提供授权码输入框或“立即授权”按钮</n-list-item>
|
<n-list-item>不提供授权码输入框或“立即授权”按钮</n-list-item>
|
||||||
<n-list-item>不提供“已授权 / 未授权 / 本地免授权”三态逻辑</n-list-item>
|
<n-list-item>不提供“已授权 / 未授权 / 本地免授权”三态逻辑</n-list-item>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader title="主题信息" desc="主题版本、运行环境、已启用模块和关键系统信息。" />
|
<PageHeader title="主题信息" desc="主题版本、运行环境、已启用模块和关键系统信息。" />
|
||||||
|
|
||||||
<div v-if="loading" class="aksrt-theme-info__loading">
|
<div v-if="loading" class="jio-theme-info__loading">
|
||||||
<n-spin size="medium" />
|
<n-spin size="medium" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- Version Card -->
|
<!-- Version Card -->
|
||||||
<n-card class="aksrt-theme-info__card" title="版本信息" size="small">
|
<n-card class="jio-theme-info__card" title="版本信息" size="small">
|
||||||
<n-descriptions :column="2" bordered label-placement="left" size="small">
|
<n-descriptions :column="2" bordered label-placement="left" size="small">
|
||||||
<n-descriptions-item label="主题版本">
|
<n-descriptions-item label="主题版本">
|
||||||
<n-tag type="success" size="small">{{ info.themeVersion }}</n-tag>
|
<n-tag type="success" size="small">{{ info.themeVersion }}</n-tag>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- Environment Card -->
|
<!-- Environment Card -->
|
||||||
<n-card class="aksrt-theme-info__card" title="运行环境" size="small">
|
<n-card class="jio-theme-info__card" title="运行环境" size="small">
|
||||||
<n-descriptions :column="2" bordered label-placement="left" size="small">
|
<n-descriptions :column="2" bordered label-placement="left" size="small">
|
||||||
<n-descriptions-item label="站点地址">
|
<n-descriptions-item label="站点地址">
|
||||||
<n-text code>{{ info.siteUrl }}</n-text>
|
<n-text code>{{ info.siteUrl }}</n-text>
|
||||||
@@ -61,14 +61,14 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- Modules Card -->
|
<!-- Modules Card -->
|
||||||
<n-card class="aksrt-theme-info__card" title="已启用模块" size="small">
|
<n-card class="jio-theme-info__card" title="已启用模块" size="small">
|
||||||
<div class="aksrt-theme-info__modules">
|
<div class="jio-theme-info__modules">
|
||||||
<n-tag
|
<n-tag
|
||||||
v-for="mod in info.modules"
|
v-for="mod in info.modules"
|
||||||
:key="mod.key"
|
:key="mod.key"
|
||||||
:type="mod.enabled ? 'success' : 'default'"
|
:type="mod.enabled ? 'success' : 'default'"
|
||||||
size="small"
|
size="small"
|
||||||
class="aksrt-theme-info__module-tag"
|
class="jio-theme-info__module-tag"
|
||||||
>
|
>
|
||||||
<FaIcon :icon="mod.enabled ? 'fa fas fa-check-circle' : 'fa fas fa-circle'" :size="10" />
|
<FaIcon :icon="mod.enabled ? 'fa fas fa-check-circle' : 'fa fas fa-circle'" :size="10" />
|
||||||
{{ mod.label }}
|
{{ mod.label }}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- Data Tables Card -->
|
<!-- Data Tables Card -->
|
||||||
<n-card class="aksrt-theme-info__card" title="数据表状态" size="small">
|
<n-card class="jio-theme-info__card" title="数据表状态" size="small">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:columns="tableColumns"
|
:columns="tableColumns"
|
||||||
:data="info.tables"
|
:data="info.tables"
|
||||||
@@ -91,25 +91,25 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<!-- Docs Links Card -->
|
<!-- Docs Links Card -->
|
||||||
<n-card class="aksrt-theme-info__card" title="快速入口" size="small">
|
<n-card class="jio-theme-info__card" title="快速入口" size="small">
|
||||||
<div class="aksrt-theme-info__links">
|
<div class="jio-theme-info__links">
|
||||||
<router-link to="/docs" class="aksrt-theme-info__link">
|
<router-link to="/docs" class="jio-theme-info__link">
|
||||||
<FaIcon icon="fa fas fa-book" :size="16" />
|
<FaIcon icon="fa fas fa-book" :size="16" />
|
||||||
<span>文档中心</span>
|
<span>文档中心</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/tools/update" class="aksrt-theme-info__link">
|
<router-link to="/tools/update" class="jio-theme-info__link">
|
||||||
<FaIcon icon="fa fas fa-cloud-download-alt" :size="16" />
|
<FaIcon icon="fa fas fa-cloud-download-alt" :size="16" />
|
||||||
<span>更新中心</span>
|
<span>更新中心</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/tools/backup" class="aksrt-theme-info__link">
|
<router-link to="/tools/backup" class="jio-theme-info__link">
|
||||||
<FaIcon icon="fa fas fa-archive" :size="16" />
|
<FaIcon icon="fa fas fa-archive" :size="16" />
|
||||||
<span>数据备份</span>
|
<span>数据备份</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/tools/health" class="aksrt-theme-info__link">
|
<router-link to="/tools/health" class="jio-theme-info__link">
|
||||||
<FaIcon icon="fa fas fa-heartbeat" :size="16" />
|
<FaIcon icon="fa fas fa-heartbeat" :size="16" />
|
||||||
<span>健康检查</span>
|
<span>健康检查</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/tools/import-export" class="aksrt-theme-info__link">
|
<router-link to="/tools/import-export" class="jio-theme-info__link">
|
||||||
<FaIcon icon="fa fas fa-exchange-alt" :size="16" />
|
<FaIcon icon="fa fas fa-exchange-alt" :size="16" />
|
||||||
<span>导入导出</span>
|
<span>导入导出</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -173,13 +173,13 @@ onMounted(async () => {
|
|||||||
const data = res?.data || res || {}
|
const data = res?.data || res || {}
|
||||||
|
|
||||||
info.value = {
|
info.value = {
|
||||||
themeVersion: window.aksrtAdminData?.themeVersion || data.theme_version || '3.4.0-dev',
|
themeVersion: window.jioAdminData?.themeVersion || data.theme_version || '3.4.0-dev',
|
||||||
wpVersion: data.wp_version || '',
|
wpVersion: data.wp_version || '',
|
||||||
phpVersion: data.php_version || '',
|
phpVersion: data.php_version || '',
|
||||||
dbSchemaVersion: data.db_schema_version || '',
|
dbSchemaVersion: data.db_schema_version || '',
|
||||||
emailBackfillDone: !!data.email_backfill_done,
|
emailBackfillDone: !!data.email_backfill_done,
|
||||||
siteUrl: data.site_url || window.aksrtAdminData?.siteUrl || '',
|
siteUrl: data.site_url || window.jioAdminData?.siteUrl || '',
|
||||||
apiUrl: data.api_url || window.aksrtAdminData?.apiUrl || '',
|
apiUrl: data.api_url || window.jioAdminData?.apiUrl || '',
|
||||||
themeDir: data.theme_dir || '',
|
themeDir: data.theme_dir || '',
|
||||||
uploadDir: data.upload_dir || '',
|
uploadDir: data.upload_dir || '',
|
||||||
isMultisite: !!data.is_multisite,
|
isMultisite: !!data.is_multisite,
|
||||||
@@ -191,8 +191,8 @@ onMounted(async () => {
|
|||||||
console.error('[ThemeInfo] Failed to load health data:', err)
|
console.error('[ThemeInfo] Failed to load health data:', err)
|
||||||
// Fallback with minimal info
|
// Fallback with minimal info
|
||||||
info.value.themeVersion = '3.4.0-dev'
|
info.value.themeVersion = '3.4.0-dev'
|
||||||
info.value.siteUrl = window.aksrtAdminData?.siteUrl || ''
|
info.value.siteUrl = window.jioAdminData?.siteUrl || ''
|
||||||
info.value.apiUrl = window.aksrtAdminData?.apiUrl || ''
|
info.value.apiUrl = window.jioAdminData?.apiUrl || ''
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -218,35 +218,35 @@ function normalizeTables(raw) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-theme-info__loading {
|
.jio-theme-info__loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 0;
|
padding: 60px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-theme-info__card {
|
.jio-theme-info__card {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-theme-info__modules {
|
.jio-theme-info__modules {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-theme-info__module-tag {
|
.jio-theme-info__module-tag {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-theme-info__links {
|
.jio-theme-info__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-theme-info__link {
|
.jio-theme-info__link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -259,7 +259,7 @@ function normalizeTables(raw) {
|
|||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-theme-info__link:hover {
|
.jio-theme-info__link:hover {
|
||||||
background: #e8eaed;
|
background: #e8eaed;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="数据备份"
|
title="数据备份"
|
||||||
desc="管理设置快照、创建手动备份,并执行恢复或删除。"
|
desc="管理设置快照、创建手动备份,并执行恢复或删除。"
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>备份策略</h3>
|
<h3>备份策略</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-space vertical size="small" style="width: 100%;">
|
<n-space vertical size="small" style="width: 100%;">
|
||||||
@@ -26,19 +26,19 @@
|
|||||||
<n-tag round :type="backups.length ? 'success' : 'default'">现有快照:{{ backups.length }} 份</n-tag>
|
<n-tag round :type="backups.length ? 'success' : 'default'">现有快照:{{ backups.length }} 份</n-tag>
|
||||||
</n-space>
|
</n-space>
|
||||||
<n-space align="center" wrap>
|
<n-space align="center" wrap>
|
||||||
<span class="aksrt-backup__label">自动备份</span>
|
<span class="jio-backup__label">自动备份</span>
|
||||||
<n-switch v-model:value="settings.backup_auto" />
|
<n-switch v-model:value="settings.backup_auto" />
|
||||||
<span class="aksrt-backup__label">频率</span>
|
<span class="jio-backup__label">频率</span>
|
||||||
<n-select v-model:value="settings.backup_frequency" :options="frequencyOptions" style="width: 140px;" />
|
<n-select v-model:value="settings.backup_frequency" :options="frequencyOptions" style="width: 140px;" />
|
||||||
<span class="aksrt-backup__label">保留</span>
|
<span class="jio-backup__label">保留</span>
|
||||||
<n-input-number v-model:value="settings.backup_retain" :min="3" :max="20" style="width: 140px;" />
|
<n-input-number v-model:value="settings.backup_retain" :min="3" :max="20" style="width: 140px;" />
|
||||||
<n-button type="primary" :loading="savingPolicy" @click="handleSavePolicy">保存策略</n-button>
|
<n-button type="primary" :loading="savingPolicy" @click="handleSavePolicy">保存策略</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>手动创建快照</h3>
|
<h3>手动创建快照</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-space vertical size="small" style="width: 100%;">
|
<n-space vertical size="small" style="width: 100%;">
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>快照列表</h3>
|
<h3>快照列表</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-empty v-if="!backups.length" description="暂无设置快照" />
|
<n-empty v-if="!backups.length" description="暂无设置快照" />
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
:single-line="false"
|
:single-line="false"
|
||||||
:scroll-x="900"
|
:scroll-x="900"
|
||||||
/>
|
/>
|
||||||
<p class="aksrt-backup__hint">
|
<p class="jio-backup__hint">
|
||||||
恢复非当前网站 / 非当前版本的快照可能导致配置异常,恢复前请确认站点环境一致。
|
恢复非当前网站 / 非当前版本的快照可能导致配置异常,恢复前请确认站点环境一致。
|
||||||
</p>
|
</p>
|
||||||
</n-card>
|
</n-card>
|
||||||
@@ -264,14 +264,14 @@ async function handleDelete(row) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-backup__hint {
|
.jio-backup__hint {
|
||||||
margin: 12px 0 0;
|
margin: 12px 0 0;
|
||||||
color: #667085;
|
color: #667085;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-backup__label {
|
.jio-backup__label {
|
||||||
color: #344054;
|
color: #344054;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="健康检查"
|
title="健康检查"
|
||||||
desc="集中查看运行环境、数据库表和系统诊断状态。"
|
desc="集中查看运行环境、数据库表和系统诊断状态。"
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
<div class="aksrt-health-grid">
|
<div class="jio-health-grid">
|
||||||
<n-card v-for="check in checkItems" :key="check.key" class="aksrt-section-card">
|
<n-card v-for="check in checkItems" :key="check.key" class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>{{ check.label }}</h3>
|
<h3>{{ check.label }}</h3>
|
||||||
<n-tag round :type="statusToTag(check.status)">{{ check.status }}</n-tag>
|
<n-tag round :type="statusToTag(check.status)">{{ check.status }}</n-tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,7 +76,7 @@ onMounted(load)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-health-grid {
|
.jio-health-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="导入导出"
|
title="导入导出"
|
||||||
desc="导出当前设置快照、执行 dry-run 导入校验,并支持按域重置到 schema 默认值。"
|
desc="导出当前设置快照、执行 dry-run 导入校验,并支持按域重置到 schema 默认值。"
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>导出与导入</h3>
|
<h3>导出与导入</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-space style="margin-bottom: 16px;">
|
<n-space style="margin-bottom: 16px;">
|
||||||
@@ -27,13 +27,13 @@
|
|||||||
:autosize="{ minRows: 12, maxRows: 20 }"
|
:autosize="{ minRows: 12, maxRows: 20 }"
|
||||||
placeholder="导出的 JSON 会出现在这里,也可以直接粘贴导入。"
|
placeholder="导出的 JSON 会出现在这里,也可以直接粘贴导入。"
|
||||||
/>
|
/>
|
||||||
<p class="aksrt-tools-hint">
|
<p class="jio-tools-hint">
|
||||||
正式导入前会自动创建一份“导入前自动快照”,与“数据备份”页的快照体系共用,便于回滚。
|
正式导入前会自动创建一份“导入前自动快照”,与“数据备份”页的快照体系共用,便于回滚。
|
||||||
</p>
|
</p>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>按域重置</h3>
|
<h3>按域重置</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-space align="center" wrap>
|
<n-space align="center" wrap>
|
||||||
@@ -41,14 +41,14 @@
|
|||||||
<n-button :loading="resetting" @click="handleReset">重置所选域</n-button>
|
<n-button :loading="resetting" @click="handleReset">重置所选域</n-button>
|
||||||
<n-button type="error" tertiary :loading="resettingAll" @click="handleResetAll">重置全部</n-button>
|
<n-button type="error" tertiary :loading="resettingAll" @click="handleResetAll">重置全部</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<p class="aksrt-tools-hint">仅支持重置 schema 托管的业务域。健康检查和迁移状态属于运维面板,不参与按域回滚。</p>
|
<p class="jio-tools-hint">仅支持重置 schema 托管的业务域。健康检查和迁移状态属于运维面板,不参与按域回滚。</p>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>最近结果</h3>
|
<h3>最近结果</h3>
|
||||||
</div>
|
</div>
|
||||||
<pre class="aksrt-tools-output">{{ resultText || '尚未执行操作。' }}</pre>
|
<pre class="jio-tools-output">{{ resultText || '尚未执行操作。' }}</pre>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,7 +169,7 @@ async function handleResetAll() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-tools-output {
|
.jio-tools-output {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -181,7 +181,7 @@ async function handleResetAll() {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-tools-hint {
|
.jio-tools-hint {
|
||||||
margin: 12px 0 0;
|
margin: 12px 0 0;
|
||||||
color: #667085;
|
color: #667085;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="更新中心"
|
title="更新中心"
|
||||||
desc="查看版本状态、检测更新并浏览更新日志与教程入口。"
|
desc="查看版本状态、检测更新并浏览更新日志与教程入口。"
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 220px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 220px;">
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>版本状态</h3>
|
<h3>版本状态</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-space>
|
<n-space>
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card" v-if="updateInfo.update_available">
|
<n-card class="jio-section-card" v-if="updateInfo.update_available">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>可用更新</h3>
|
<h3>可用更新</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-alert type="warning" style="margin-bottom: 12px;">
|
<n-alert type="warning" style="margin-bottom: 12px;">
|
||||||
@@ -39,18 +39,18 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
<n-button @click="handleDismiss" :loading="dismissing">忽略此版本</n-button>
|
<n-button @click="handleDismiss" :loading="dismissing">忽略此版本</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<div v-if="updateInfo.changelog" class="aksrt-update__changelog">
|
<div v-if="updateInfo.changelog" class="jio-update__changelog">
|
||||||
<h4>更新日志</h4>
|
<h4>更新日志</h4>
|
||||||
<pre>{{ updateInfo.changelog }}</pre>
|
<pre>{{ updateInfo.changelog }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>常用教程入口</h3>
|
<h3>常用教程入口</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-update__grid">
|
<div class="jio-update__grid">
|
||||||
<a v-for="item in guides" :key="item.key" class="aksrt-update__guide" :href="item.url" target="_blank" rel="noreferrer">
|
<a v-for="item in guides" :key="item.key" class="jio-update__guide" :href="item.url" target="_blank" rel="noreferrer">
|
||||||
<strong>{{ item.title }}</strong>
|
<strong>{{ item.title }}</strong>
|
||||||
<span>{{ item.desc }}</span>
|
<span>{{ item.desc }}</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -152,11 +152,11 @@ async function handleDismiss() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-update__changelog {
|
.jio-update__changelog {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-update__changelog pre {
|
.jio-update__changelog pre {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -168,30 +168,30 @@ async function handleDismiss() {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-update__grid {
|
.jio-update__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-update__guide {
|
.jio-update__guide {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
border: 1px solid var(--aksrt-color-border);
|
border: 1px solid var(--jio-color-border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-update__guide:hover {
|
.jio-update__guide:hover {
|
||||||
border-color: var(--aksrt-color-primary);
|
border-color: var(--jio-color-primary);
|
||||||
background: #f5faff;
|
background: #f5faff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-update__guide span {
|
.jio-update__guide span {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #667085;
|
color: #667085;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader title="用户徽章" desc="配置勋章分类、自动授予条件与前台公开展示。">
|
<PageHeader title="用户徽章" desc="配置勋章分类、自动授予条件与前台公开展示。">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<n-space>
|
<n-space>
|
||||||
@@ -10,11 +10,11 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>徽章展示</h3>
|
<h3>徽章展示</h3>
|
||||||
</div>
|
</div>
|
||||||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
<n-form :model="form" label-placement="left" label-width="180" class="jio-form">
|
||||||
<n-form-item label="启用徽章/勋章系统">
|
<n-form-item label="启用徽章/勋章系统">
|
||||||
<n-switch v-model:value="form.user_medal_s" />
|
<n-switch v-model:value="form.user_medal_s" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -31,8 +31,8 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card v-if="form.user_medal_s" class="aksrt-section-card">
|
<n-card v-if="form.user_medal_s" class="jio-section-card">
|
||||||
<div class="aksrt-section-header users-badge__header">
|
<div class="jio-section-header users-badge__header">
|
||||||
<h3>徽章明细</h3>
|
<h3>徽章明细</h3>
|
||||||
<span class="users-badge__stats">{{ form.user_medal_args.length }} 个分类 · {{ itemCount }} 枚徽章</span>
|
<span class="users-badge__stats">{{ form.user_medal_args.length }} 个分类 · {{ itemCount }} 枚徽章</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
拖动手柄调整分类与条目顺序,点击行头展开编辑。获取条件选择「仅手动授予」时不自动发放。
|
拖动手柄调整分类与条目顺序,点击行头展开编辑。获取条件选择「仅手动授予」时不自动发放。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="!form.user_medal_args.length" class="aksrt-sortable__empty">
|
<div v-if="!form.user_medal_args.length" class="jio-sortable__empty">
|
||||||
<i class="fa fas fa-trophy" aria-hidden="true" />
|
<i class="fa fas fa-trophy" aria-hidden="true" />
|
||||||
<span>暂无分类,请添加</span>
|
<span>暂无分类,请添加</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
v-model="form.user_medal_args"
|
v-model="form.user_medal_args"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
:item-key="rowKey"
|
:item-key="rowKey"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<MedalCategoryCard
|
<MedalCategoryCard
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
|
||||||
<AksSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="load" />
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="load" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ import { useMessage } from 'naive-ui'
|
|||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
import AksSaveBar from '@/components/form/AksSaveBar.vue'
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||||
import { useCollapsibleItems } from '@/components/form/useCollapsibleItems.js'
|
import { useCollapsibleItems } from '@/components/form/useCollapsibleItems.js'
|
||||||
import { defaultMedalArgs } from '@/schema/sections/users/badge.js'
|
import { defaultMedalArgs } from '@/schema/sections/users/badge.js'
|
||||||
@@ -176,13 +176,13 @@ onMounted(load)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.users-badge__stats {
|
.users-badge__stats {
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.users-badge__hint {
|
.users-badge__hint {
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
color: var(--aksrt-color-muted, #6F7A72);
|
color: var(--jio-color-muted, #6F7A72);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="用户等级"
|
title="用户等级"
|
||||||
desc="配置等级开关、最高等级、升级经验阈值与经验获取矩阵(对齐 legacy 用户等级 / 经验值)。"
|
desc="配置等级开关、最高等级、升级经验阈值与经验获取矩阵(对齐 legacy 用户等级 / 经验值)。"
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>当前摘要</h3></div>
|
<div class="jio-section-header"><h3>当前摘要</h3></div>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-tag round :type="form.user_level_s ? 'success' : 'default'">
|
<n-tag round :type="form.user_level_s ? 'success' : 'default'">
|
||||||
等级系统:{{ form.user_level_s ? '已启用' : '已关闭' }}
|
等级系统:{{ form.user_level_s ? '已启用' : '已关闭' }}
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>等级显示</h3></div>
|
<div class="jio-section-header"><h3>等级显示</h3></div>
|
||||||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
<n-form :model="form" label-placement="left" label-width="180" class="jio-form">
|
||||||
<n-form-item label="启用用户等级">
|
<n-form-item label="启用用户等级">
|
||||||
<n-switch v-model:value="form.user_level_s" />
|
<n-switch v-model:value="form.user_level_s" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -42,17 +42,17 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card v-if="form.user_level_s" class="aksrt-section-card">
|
<n-card v-if="form.user_level_s" class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>升级所需经验(累计)</h3></div>
|
<div class="jio-section-header"><h3>升级所需经验(累计)</h3></div>
|
||||||
<n-alert type="info" :show-icon="false" style="margin-bottom: 12px;">
|
<n-alert type="info" :show-icon="false" style="margin-bottom: 12px;">
|
||||||
Lv.1 通常为 0。数值为升到该级所需的累计经验。修改最高等级后会自动补齐/截断行。
|
Lv.1 通常为 0。数值为升到该级所需的累计经验。修改最高等级后会自动补齐/截断行。
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-data-table :columns="levelColumns" :data="levelRows" :bordered="false" size="small" />
|
<n-data-table :columns="levelColumns" :data="levelRows" :bordered="false" size="small" />
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card v-if="form.user_level_s" class="aksrt-section-card">
|
<n-card v-if="form.user_level_s" class="jio-section-card">
|
||||||
<div class="aksrt-section-header"><h3>经验值得分</h3></div>
|
<div class="jio-section-header"><h3>经验值得分</h3></div>
|
||||||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
<n-form :model="form" label-placement="left" label-width="180" class="jio-form">
|
||||||
<n-form-item label="每日上限">
|
<n-form-item label="每日上限">
|
||||||
<n-input-number v-model:value="form.user_integral_opt.day_max" :min="0" :max="10000" style="width: 180px;" />
|
<n-input-number v-model:value="form.user_integral_opt.day_max" :min="0" :max="10000" style="width: 180px;" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -70,11 +70,8 @@
|
|||||||
</n-grid>
|
</n-grid>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<div class="aksrt-form-actions">
|
|
||||||
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
|
|
||||||
</div>
|
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="load" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -84,6 +81,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import { NInput, NInputNumber, useMessage } from 'naive-ui'
|
import { NInput, NInputNumber, useMessage } from 'naive-ui'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
import {
|
import {
|
||||||
INTEGRAL_CATALOG,
|
INTEGRAL_CATALOG,
|
||||||
defaultIntegralOpt,
|
defaultIntegralOpt,
|
||||||
@@ -104,6 +102,13 @@ const form = reactive({
|
|||||||
user_level_notify: true,
|
user_level_notify: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const snapshot = ref('')
|
||||||
|
const serialize = () => JSON.stringify(form)
|
||||||
|
const dirty = computed(() => !loading.value && snapshot.value !== '' && serialize() !== snapshot.value)
|
||||||
|
function markClean() {
|
||||||
|
snapshot.value = serialize()
|
||||||
|
}
|
||||||
|
|
||||||
const integralGroups = computed(() => [...new Set(INTEGRAL_CATALOG.map((r) => r.group))])
|
const integralGroups = computed(() => [...new Set(INTEGRAL_CATALOG.map((r) => r.group))])
|
||||||
|
|
||||||
const levelRows = computed(() => {
|
const levelRows = computed(() => {
|
||||||
@@ -140,7 +145,7 @@ const levelColumns = [
|
|||||||
h(NInput, {
|
h(NInput, {
|
||||||
value: form.user_level_opt['icon_img_' + row.level] ?? '',
|
value: form.user_level_opt['icon_img_' + row.level] ?? '',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
placeholder: row.level <= 10 ? '留空使用主题内置图标' : '请输入图片 URL',
|
placeholder: row.level <= 10 ? '默认使用主题自带等级图标' : '请输入图片 URL',
|
||||||
onUpdateValue: (v) => {
|
onUpdateValue: (v) => {
|
||||||
form.user_level_opt['icon_img_' + row.level] = v
|
form.user_level_opt['icon_img_' + row.level] = v
|
||||||
},
|
},
|
||||||
@@ -245,6 +250,7 @@ async function load() {
|
|||||||
form.user_level_notify = d.user_level_notify !== false
|
form.user_level_notify = d.user_level_notify !== false
|
||||||
form.user_level_opt = normalizeLevelOpt(d.user_level_opt, form.user_level_max)
|
form.user_level_opt = normalizeLevelOpt(d.user_level_opt, form.user_level_max)
|
||||||
form.user_integral_opt = normalizeIntegral(d.user_integral_opt)
|
form.user_integral_opt = normalizeIntegral(d.user_integral_opt)
|
||||||
|
markClean()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message || '加载用户等级配置失败')
|
message.error(error.message || '加载用户等级配置失败')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -270,6 +276,7 @@ async function handleSave() {
|
|||||||
user_level_opt: payload.user_level_opt,
|
user_level_opt: payload.user_level_opt,
|
||||||
user_integral_opt: payload.user_integral_opt,
|
user_integral_opt: payload.user_integral_opt,
|
||||||
})
|
})
|
||||||
|
markClean()
|
||||||
message.success('用户等级配置已保存')
|
message.success('用户等级配置已保存')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message || '保存失败')
|
message.error(error.message || '保存失败')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="工单中心"
|
title="工单中心"
|
||||||
desc="工单功能开关、提交公告与提交表单配置,以及部门、状态、预设回复三块字典维护。工单的日常处理在 WordPress 用户菜单的「工单管理」中完成。"
|
desc="工单功能开关、提交公告与提交表单配置,以及部门、状态、预设回复三块字典维护。工单的日常处理在 WordPress 用户菜单的「工单管理」中完成。"
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<n-tabs v-model:value="activeTab" type="segment" animated class="aksrt-ticket-tabs">
|
<n-tabs v-model:value="activeTab" type="segment" animated class="jio-ticket-tabs">
|
||||||
<n-tab-pane name="settings" tab="基础配置">
|
<n-tab-pane name="settings" tab="基础配置">
|
||||||
<SchemaPage domain="users" section="ticket" />
|
<SchemaPage domain="users" section="ticket" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
@@ -57,13 +57,13 @@ watch(activeTab, (name) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function openNativeList() {
|
function openNativeList() {
|
||||||
const base = (window.aksrtAdminData?.adminUrl || '/wp-admin/').replace(/\/?$/, '/')
|
const base = (window.jioAdminData?.adminUrl || '/wp-admin/').replace(/\/?$/, '/')
|
||||||
window.location.href = base + 'users.php?page=aksrt_tickets'
|
window.location.href = base + 'users.php?page=jio_tickets'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-ticket-tabs {
|
.jio-ticket-tabs {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader title="身份认证" desc="配置通用认证申请规则;申请审核和用户认证资料管理位于 WordPress 用户菜单。" />
|
<PageHeader title="身份认证" desc="配置通用认证申请规则;申请审核和用户认证资料管理位于 WordPress 用户菜单。" />
|
||||||
<n-spin :show="loading" style="width: 100%; min-height: 240px">
|
<n-spin :show="loading" style="width: 100%; min-height: 240px">
|
||||||
<n-card class="aksrt-section-card" title="认证申请设置">
|
<n-card class="jio-section-card" title="认证申请设置">
|
||||||
<n-form :model="form" label-placement="left" label-width="190">
|
<n-form :model="form" label-placement="left" label-width="190">
|
||||||
<n-form-item label="启用身份认证"><n-switch v-model:value="form.user_auth_s" /></n-form-item>
|
<n-form-item label="启用身份认证"><n-switch v-model:value="form.user_auth_s" /></n-form-item>
|
||||||
<n-form-item label="允许用户前台申请"><n-switch v-model:value="form.user_auth_apply_s" :disabled="!form.user_auth_s" /></n-form-item>
|
<n-form-item label="允许用户前台申请"><n-switch v-model:value="form.user_auth_apply_s" :disabled="!form.user_auth_s" /></n-form-item>
|
||||||
@@ -18,11 +18,8 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
<div class="aksrt-form-actions">
|
|
||||||
<n-button :disabled="!dirty || saving" @click="load">放弃更改</n-button>
|
|
||||||
<n-button type="primary" :disabled="!dirty" :loading="saving" @click="save">保存设置</n-button>
|
|
||||||
</div>
|
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="save" @reload="load" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -30,6 +27,7 @@
|
|||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
@@ -45,7 +43,7 @@ const initial = ref('')
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const snapshot = computed(() => JSON.stringify(form))
|
const snapshot = computed(() => JSON.stringify(form))
|
||||||
const dirty = computed(() => initial.value !== '' && snapshot.value !== initial.value)
|
const dirty = computed(() => !loading.value && initial.value !== '' && snapshot.value !== initial.value)
|
||||||
|
|
||||||
function extractData(response) {
|
function extractData(response) {
|
||||||
return response?.data ?? response ?? {}
|
return response?.data ?? response ?? {}
|
||||||
|
|||||||
@@ -1,257 +1,195 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-page-container">
|
<div class="jio-page-container">
|
||||||
<PageHeader title="微信模板消息" desc="配置微信公众号模板消息推送。" />
|
<PageHeader title="微信模板消息" desc="按通知事件配置公众号模板及参数。" />
|
||||||
|
|
||||||
<!-- 状态卡片 -->
|
<n-spin :show="loading" style="width: 100%; min-height: 240px">
|
||||||
<div class="aksrt-wechat__status">
|
<n-alert v-if="loadError" type="error" class="wechat-notice">
|
||||||
<div class="aksrt-wechat__status-card">
|
{{ loadError }}
|
||||||
<div class="aksrt-wechat__status-icon" :class="status.configured ? 'aksrt-wechat__status-icon--ok' : 'aksrt-wechat__status-icon--warn'">
|
<template #action><n-button size="small" @click="load">重试</n-button></template>
|
||||||
<i :class="status.configured ? 'fa fas fa-check' : 'fa fas fa-exclamation'"></i>
|
</n-alert>
|
||||||
</div>
|
<template v-else>
|
||||||
<div class="aksrt-wechat__status-info">
|
<n-alert :type="oauthReady ? 'info' : 'warning'" class="wechat-notice">
|
||||||
<div class="aksrt-wechat__status-label">配置状态</div>
|
<div>模板消息依赖已启用的微信公众号登录服务号,以及公众号的模板消息权限。</div>
|
||||||
<div class="aksrt-wechat__status-value">{{ status.configured ? '已配置' : '未配置' }}</div>
|
<div>只有绑定并关注该公众号的用户才能收到通知。当前账号:{{ currentUserBound ? '已绑定' : '未绑定' }};已绑定用户:{{ boundUsers }} 人。</div>
|
||||||
</div>
|
<template #action>
|
||||||
</div>
|
<router-link to="/users/oauth">公众号登录设置</router-link>
|
||||||
<div class="aksrt-wechat__status-card">
|
</template>
|
||||||
<div class="aksrt-wechat__status-icon" :class="status.has_token ? 'aksrt-wechat__status-icon--ok' : 'aksrt-wechat__status-icon--warn'">
|
</n-alert>
|
||||||
<i :class="status.has_token ? 'fa fas fa-key' : 'fa fas fa-key'"></i>
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-wechat__status-info">
|
|
||||||
<div class="aksrt-wechat__status-label">Access Token</div>
|
|
||||||
<div class="aksrt-wechat__status-value">{{ status.has_token ? '已获取' : '未获取' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-wechat__status-card">
|
|
||||||
<div class="aksrt-wechat__status-icon" :class="status.has_template ? 'aksrt-wechat__status-icon--ok' : 'aksrt-wechat__status-icon--warn'">
|
|
||||||
<i :class="status.has_template ? 'fa fas fa-file-alt' : 'fa fas fa-file-alt'"></i>
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-wechat__status-info">
|
|
||||||
<div class="aksrt-wechat__status-label">模板 ID</div>
|
|
||||||
<div class="aksrt-wechat__status-value">{{ status.has_template ? '已配置' : '未配置' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-wechat__status-card">
|
|
||||||
<div class="aksrt-wechat__status-icon aksrt-wechat__status-icon--info">
|
|
||||||
<i class="fa fas fa-users"></i>
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-wechat__status-info">
|
|
||||||
<div class="aksrt-wechat__status-label">绑定用户</div>
|
|
||||||
<div class="aksrt-wechat__status-value">{{ status.bound_users || 0 }} 人</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 配置表单 -->
|
<section class="wechat-section">
|
||||||
<div class="aksrt-wechat__form-card">
|
<div class="wechat-section__head">
|
||||||
<h3>基本配置</h3>
|
<h3>消息推送</h3>
|
||||||
<div class="aksrt-form-group">
|
<n-switch v-model:value="form.enabled" :disabled="loading" aria-label="启用微信模板消息" />
|
||||||
<label>
|
</div>
|
||||||
<input type="checkbox" v-model="form.enabled"> 启用微信模板消息
|
<p class="wechat-muted">通过微信公众号将网站通知推送给绑定用户。配置模板后保存,测试会发送到当前管理员账号。</p>
|
||||||
</label>
|
</section>
|
||||||
</div>
|
|
||||||
<div class="aksrt-form-group">
|
|
||||||
<label>AppID</label>
|
|
||||||
<input v-model="form.app_id" type="text" class="aksrt-input" placeholder="微信公众号 AppID">
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-form-group">
|
|
||||||
<label>AppSecret</label>
|
|
||||||
<input v-model="form.app_secret" type="password" class="aksrt-input" placeholder="微信公众号 AppSecret">
|
|
||||||
<p class="aksrt-form-hint">保存后不会回显完整密钥</p>
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-form-group">
|
|
||||||
<label>模板 ID</label>
|
|
||||||
<input v-model="form.template_id" type="text" class="aksrt-input" placeholder="微信模板消息模板 ID">
|
|
||||||
</div>
|
|
||||||
<div class="aksrt-form-actions">
|
|
||||||
<button class="aksrt-button aksrt-button--primary" :disabled="saving" @click="saveSettings">
|
|
||||||
{{ saving ? '保存中...' : '保存设置' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 通知类型 -->
|
<section v-for="group in groups" :key="group.name" class="wechat-section">
|
||||||
<div class="aksrt-wechat__form-card">
|
<h3>{{ group.name }}</h3>
|
||||||
<h3>支持的通知类型</h3>
|
<div v-for="type in group.types" :key="type.key" class="wechat-template">
|
||||||
<p class="aksrt-form-hint">以下事件类型可触发微信模板消息推送(需用户绑定微信且开启对应订阅):</p>
|
<div class="wechat-template__head">
|
||||||
<table class="aksrt-table aksrt-table--sm">
|
<div>
|
||||||
<thead>
|
<strong>{{ type.label }}</strong>
|
||||||
<tr>
|
<span class="wechat-template__key">{{ type.key }}</span>
|
||||||
<th>事件类型</th>
|
</div>
|
||||||
<th>说明</th>
|
<n-switch v-model:value="form.templates[type.key].enabled" :disabled="!form.enabled" :aria-label="`启用${type.label}`" />
|
||||||
<th>状态</th>
|
</div>
|
||||||
</tr>
|
<div v-if="form.enabled && form.templates[type.key].enabled" class="wechat-template__body">
|
||||||
</thead>
|
<label class="wechat-field">
|
||||||
<tbody>
|
<span>模板 ID</span>
|
||||||
<tr v-for="evt in eventTypes" :key="evt.key">
|
<n-input v-model:value="form.templates[type.key].id" placeholder="公众号模板消息 ID" maxlength="128" clearable />
|
||||||
<td><code>{{ evt.key }}</code></td>
|
</label>
|
||||||
<td>{{ evt.desc }}</td>
|
<div class="wechat-field">
|
||||||
<td>
|
<span>模板参数</span>
|
||||||
<span class="aksrt-badge" :class="evt.ready ? 'aksrt-badge--success' : 'aksrt-badge--warning'">
|
<div class="wechat-params">
|
||||||
{{ evt.ready ? '可用' : '待接入' }}
|
<label v-for="field in type.fields" :key="field.key" class="wechat-param">
|
||||||
</span>
|
<span>{{ field.label }} <code>{{ field.key }}</code></span>
|
||||||
</td>
|
<n-input v-model:value="form.templates[type.key].keys[field.key]" :placeholder="`留空使用 keyword${field.index}`" maxlength="64" clearable />
|
||||||
</tr>
|
</label>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
<small>填写公众号模板中的参数名,例如 thing1、time2;留空时依次使用 keyword1、keyword2。</small>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="wechat-section wechat-test">
|
||||||
|
<h3>发送测试</h3>
|
||||||
|
<p class="wechat-muted">先保存配置,再选择已启用的模板。测试消息会发送到当前管理员绑定的公众号账号。</p>
|
||||||
|
<div class="wechat-test__controls">
|
||||||
|
<n-select v-model:value="testType" :options="testOptions" placeholder="选择需要测试的模板" :disabled="!testOptions.length" />
|
||||||
|
<n-button type="primary" :loading="testing" :disabled="dirty || !testType || !currentUserBound || !oauthReady || !form.enabled" @click="sendTest">发送测试</n-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
</n-spin>
|
||||||
|
<JioSaveBar :dirty="dirty" :saving="saving" @save="save" @reload="load" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import PageHeader from '@/components/PageHeader.vue';
|
import { useMessage } from 'naive-ui'
|
||||||
import api from '@/api';
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
|
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||||
|
import api from '@/api'
|
||||||
|
|
||||||
export default {
|
const message = useMessage()
|
||||||
name: 'UsersWechatTemplate',
|
const loading = ref(true)
|
||||||
components: { PageHeader },
|
const saving = ref(false)
|
||||||
setup() {
|
const testing = ref(false)
|
||||||
const status = ref({
|
const loadError = ref('')
|
||||||
enabled: false,
|
const oauthReady = ref(false)
|
||||||
configured: false,
|
const currentUserBound = ref(false)
|
||||||
has_token: false,
|
const boundUsers = ref(0)
|
||||||
has_template: false,
|
const definitions = ref({})
|
||||||
bound_users: 0,
|
const form = reactive({ enabled: false, templates: {} })
|
||||||
});
|
const initial = ref('')
|
||||||
|
const testType = ref(null)
|
||||||
|
const snapshot = computed(() => JSON.stringify(form))
|
||||||
|
const dirty = computed(() => !loading.value && initial.value !== '' && initial.value !== snapshot.value)
|
||||||
|
|
||||||
const form = reactive({
|
const groups = computed(() => {
|
||||||
enabled: false,
|
const grouped = new Map()
|
||||||
app_id: '',
|
for (const [key, definition] of Object.entries(definitions.value)) {
|
||||||
app_secret: '',
|
const name = definition.group || '其他'
|
||||||
template_id: '',
|
if (!grouped.has(name)) grouped.set(name, [])
|
||||||
});
|
grouped.get(name).push({
|
||||||
|
key,
|
||||||
|
label: definition.label,
|
||||||
|
fields: Object.entries(definition.fields || {}).map(([fieldKey, label], index) => ({ key: fieldKey, label, index: index + 1 })),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Array.from(grouped, ([name, types]) => ({ name, types }))
|
||||||
|
})
|
||||||
|
|
||||||
const saving = ref(false);
|
const testOptions = computed(() => Object.entries(definitions.value)
|
||||||
|
.filter(([key]) => form.templates[key]?.enabled && form.templates[key]?.id)
|
||||||
|
.map(([key, definition]) => ({ label: definition.label, value: key })))
|
||||||
|
|
||||||
// Event type definitions — readiness driven by backend allowed_types
|
async function load() {
|
||||||
const eventTypeDefs = [
|
loading.value = true
|
||||||
{ key: 'comment_reply', desc: '评论回复通知' },
|
loadError.value = ''
|
||||||
{ key: 'system_notice', desc: '系统公告通知' },
|
try {
|
||||||
{ key: 'order_paid', desc: '订单支付通知' },
|
const response = await api.request('/wechat/status')
|
||||||
{ key: 'credits_change', desc: '积分变动通知' },
|
const data = response.data || {}
|
||||||
];
|
definitions.value = data.types || {}
|
||||||
|
form.enabled = Boolean(data.config?.enabled)
|
||||||
const allowedTypes = ref([]);
|
form.templates = structuredClone(data.config?.templates || {})
|
||||||
|
oauthReady.value = Boolean(data.oauth_ready)
|
||||||
const eventTypes = computed(() =>
|
currentUserBound.value = Boolean(data.current_user_bound)
|
||||||
eventTypeDefs.map(et => ({
|
boundUsers.value = Number(data.bound_users || 0)
|
||||||
...et,
|
if (!testOptions.value.some(option => option.value === testType.value)) {
|
||||||
ready: allowedTypes.value.length === 0 || allowedTypes.value.includes(et.key),
|
testType.value = testOptions.value[0]?.value || null
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
async function loadStatus() {
|
|
||||||
try {
|
|
||||||
const json = await api.request('/wechat/status');
|
|
||||||
if (json.success) {
|
|
||||||
status.value = json.data;
|
|
||||||
form.enabled = json.data.enabled || false;
|
|
||||||
form.app_id = json.data.appid || '';
|
|
||||||
form.app_secret = json.data.app_secret === '***' ? '' : '';
|
|
||||||
form.template_id = json.data.template_id || '';
|
|
||||||
// Use backend allowed_types for readiness (empty = all allowed)
|
|
||||||
allowedTypes.value = json.data.allowed_types || [];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Load WeChat status failed:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
initial.value = snapshot.value
|
||||||
|
} catch (error) {
|
||||||
|
loadError.value = error.message || '加载微信模板消息设置失败'
|
||||||
|
message.error(loadError.value)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function saveSettings() {
|
async function save() {
|
||||||
saving.value = true;
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
const payload = { ...form };
|
const response = await api.request('/wechat/settings', { method: 'POST', data: JSON.parse(snapshot.value) })
|
||||||
// 如果密码为空且状态显示已配置,发送掩码保留原值
|
const config = response.data || {}
|
||||||
if (!payload.app_secret && status.value.configured) {
|
form.enabled = Boolean(config.enabled)
|
||||||
payload.app_secret = '***';
|
form.templates = structuredClone(config.templates || {})
|
||||||
}
|
if (!testOptions.value.some(option => option.value === testType.value)) {
|
||||||
const json = await api.request('/wechat/settings', {
|
testType.value = testOptions.value[0]?.value || null
|
||||||
method: 'POST',
|
|
||||||
data: payload,
|
|
||||||
});
|
|
||||||
if (json.success) {
|
|
||||||
alert('设置已保存');
|
|
||||||
loadStatus();
|
|
||||||
} else {
|
|
||||||
alert(json.message || '保存失败');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Save failed:', e);
|
|
||||||
alert('保存失败: ' + (e.message || '未知错误'));
|
|
||||||
} finally {
|
|
||||||
saving.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
initial.value = snapshot.value
|
||||||
|
message.success('微信模板消息设置已保存')
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || '保存失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => loadStatus());
|
async function sendTest() {
|
||||||
|
testing.value = true
|
||||||
|
try {
|
||||||
|
await api.request('/wechat/test', { method: 'POST', data: { type: testType.value } })
|
||||||
|
message.success('测试消息已发送')
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || '测试发送失败')
|
||||||
|
} finally {
|
||||||
|
testing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { status, form, saving, eventTypes, saveSettings };
|
onMounted(load)
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-wechat__status {
|
.wechat-notice { margin-bottom: 20px; }
|
||||||
display: grid;
|
.wechat-section { border-top: 1px solid #e6e9ed; padding: 20px 0; }
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
.wechat-section h3 { margin: 0 0 12px; font-size: 16px; font-weight: 600; }
|
||||||
gap: 16px;
|
.wechat-section__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
||||||
margin-bottom: 24px;
|
.wechat-section__head h3 { margin: 0; }
|
||||||
}
|
.wechat-muted { margin: 8px 0 0; color: #64707d; font-size: 13px; line-height: 1.6; }
|
||||||
.aksrt-wechat__status-card {
|
.wechat-template { border-bottom: 1px solid #edf0f2; padding: 14px 0; }
|
||||||
display: flex;
|
.wechat-template:last-child { border-bottom: 0; }
|
||||||
align-items: center;
|
.wechat-template__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 32px; }
|
||||||
gap: 12px;
|
.wechat-template__head strong { font-size: 14px; font-weight: 600; }
|
||||||
padding: 16px;
|
.wechat-template__key { display: block; color: #7b8590; font-size: 11px; margin-top: 3px; }
|
||||||
background: #fff;
|
.wechat-template__body { display: grid; grid-template-columns: minmax(200px, 320px) minmax(0, 1fr); gap: 24px; padding: 16px 0 4px; }
|
||||||
border-radius: 8px;
|
.wechat-field { display: flex; flex-direction: column; gap: 7px; min-width: 0; font-size: 13px; }
|
||||||
border: 1px solid #e8e8e8;
|
.wechat-field > span { font-weight: 600; }
|
||||||
}
|
.wechat-field small { color: #64707d; line-height: 1.5; }
|
||||||
.aksrt-wechat__status-icon {
|
.wechat-params { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
||||||
width: 40px;
|
.wechat-param { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
||||||
height: 40px;
|
.wechat-param span { color: #4c5966; font-size: 12px; }
|
||||||
border-radius: 8px;
|
.wechat-param code { color: #7b8590; font-size: 11px; }
|
||||||
display: flex;
|
.wechat-test__controls { display: flex; gap: 10px; margin-top: 14px; max-width: 520px; }
|
||||||
align-items: center;
|
.wechat-test__controls :deep(.n-select) { flex: 1; min-width: 0; }
|
||||||
justify-content: center;
|
@media (max-width: 780px) {
|
||||||
font-size: 16px;
|
.wechat-template__body { grid-template-columns: 1fr; gap: 16px; }
|
||||||
}
|
.wechat-test__controls { flex-wrap: wrap; }
|
||||||
.aksrt-wechat__status-icon--ok {
|
.wechat-test__controls :deep(.n-select) { flex-basis: 100%; }
|
||||||
background: #d4edda;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
.aksrt-wechat__status-icon--warn {
|
|
||||||
background: #fff3cd;
|
|
||||||
color: #856404;
|
|
||||||
}
|
|
||||||
.aksrt-wechat__status-icon--info {
|
|
||||||
background: #d1ecf1;
|
|
||||||
color: #0c5460;
|
|
||||||
}
|
|
||||||
.aksrt-wechat__status-value {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--aksrt-color-text);
|
|
||||||
}
|
|
||||||
.aksrt-wechat__status-label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
.aksrt-wechat__form-card {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.aksrt-wechat__form-card h3 {
|
|
||||||
margin: 0 0 16px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.aksrt-form-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #888;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head" v-bind="headAttrs">
|
<div class="jio-sortable__head" v-bind="headAttrs">
|
||||||
<AksSortHandle @click.stop />
|
<JioSortHandle @click.stop />
|
||||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||||
<div class="aksrt-sortable__main">
|
<div class="jio-sortable__main">
|
||||||
<div class="aksrt-sortable__title-row">
|
<div class="jio-sortable__title-row">
|
||||||
<span class="aksrt-sortable__title">{{ category.cat_name || '未命名分类' }}</span>
|
<span class="jio-sortable__title">{{ category.cat_name || '未命名分类' }}</span>
|
||||||
<n-tag size="small" :bordered="false">{{ category.items.length }} 枚</n-tag>
|
<n-tag size="small" :bordered="false">{{ category.items.length }} 枚</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<span class="aksrt-sortable__desc">{{ previewNames || '该分类暂无条目' }}</span>
|
<span class="jio-sortable__desc">{{ previewNames || '该分类暂无条目' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-sortable__actions">
|
<div class="jio-sortable__actions">
|
||||||
<n-popconfirm @positive-click="emit('remove')">
|
<n-popconfirm @positive-click="emit('remove')">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button size="tiny" quaternary type="error" title="删除分类" @click.stop>
|
<n-button size="tiny" quaternary type="error" title="删除分类" @click.stop>
|
||||||
@@ -20,17 +20,17 @@
|
|||||||
删除分类「{{ category.cat_name || '未命名分类' }}」及其全部条目?
|
删除分类「{{ category.cat_name || '未命名分类' }}」及其全部条目?
|
||||||
</n-popconfirm>
|
</n-popconfirm>
|
||||||
</div>
|
</div>
|
||||||
<i class="fa fas aksrt-sortable__toggle" :class="open ? 'fa-chevron-down' : 'fa-chevron-right'" aria-hidden="true" />
|
<i class="fa fas jio-sortable__toggle" :class="open ? 'fa-chevron-down' : 'fa-chevron-right'" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="open" class="aksrt-sortable__body">
|
<div v-if="open" class="jio-sortable__body">
|
||||||
<n-form label-placement="left" label-width="96" class="aksrt-form">
|
<n-form label-placement="left" label-width="96" class="jio-form">
|
||||||
<n-form-item label="分类名称">
|
<n-form-item label="分类名称">
|
||||||
<n-input v-model:value="category.cat_name" placeholder="如:成长" style="max-width: 320px;" />
|
<n-input v-model:value="category.cat_name" placeholder="如:成长" style="max-width: 320px;" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
|
||||||
<div v-if="!category.items.length" class="aksrt-sortable__empty">
|
<div v-if="!category.items.length" class="jio-sortable__empty">
|
||||||
<i class="fa fas fa-medal" aria-hidden="true" />
|
<i class="fa fas fa-medal" aria-hidden="true" />
|
||||||
<span>该分类暂无条目</span>
|
<span>该分类暂无条目</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
v-model="category.items"
|
v-model="category.items"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
:item-key="rowKey"
|
:item-key="rowKey"
|
||||||
class="aksrt-sortable aksrt-sortable--compact"
|
class="jio-sortable jio-sortable--compact"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index: ii }">
|
<template #item="{ element, index: ii }">
|
||||||
<MedalItemCard
|
<MedalItemCard
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
/** 徽章分类卡片:行头显示条目数与名称预览,展开后编辑分类名并拖拽排序条目。 */
|
/** 徽章分类卡片:行头显示条目数与名称预览,展开后编辑分类名并拖拽排序条目。 */
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||||
import { useCollapsibleItems } from '@/components/form/useCollapsibleItems.js'
|
import { useCollapsibleItems } from '@/components/form/useCollapsibleItems.js'
|
||||||
import { emptyItem, rowKey } from './badge-model.js'
|
import { emptyItem, rowKey } from './badge-model.js'
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<img :src="src" :alt="`预设图标 ${i + 1}`" />
|
<img :src="src" :alt="`预设图标 ${i + 1}`" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<AksMediaField
|
<JioMediaField
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
placeholder="或从媒体库选择自定义图标"
|
placeholder="或从媒体库选择自定义图标"
|
||||||
@update:model-value="emit('update:modelValue', $event)"
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/** 勋章图标选择:主题预设 SVG 一键选用,或走媒体库 / 直接填 URL。 */
|
/** 勋章图标选择:主题预设 SVG 一键选用,或走媒体库 / 直接填 URL。 */
|
||||||
import AksMediaField from '@/components/form/AksMediaField.vue'
|
import JioMediaField from '@/components/form/JioMediaField.vue'
|
||||||
import { MEDAL_PRESET_ICONS } from '@/schema/sections/users/badge.js'
|
import { MEDAL_PRESET_ICONS } from '@/schema/sections/users/badge.js'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
@@ -55,9 +55,9 @@ const emit = defineEmits(['update:modelValue'])
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 1px solid var(--aksrt-color-border, #E2E8E3);
|
border: 1px solid var(--jio-color-border, #E2E8E3);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--aksrt-color-surface, #fff);
|
background: var(--jio-color-surface, #fff);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color .15s ease, background-color .15s ease;
|
transition: border-color .15s ease, background-color .15s ease;
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ const emit = defineEmits(['update:modelValue'])
|
|||||||
|
|
||||||
.medal-icon-picker__preset:hover,
|
.medal-icon-picker__preset:hover,
|
||||||
.medal-icon-picker__preset:focus-visible {
|
.medal-icon-picker__preset:focus-visible {
|
||||||
border-color: var(--aksrt-color-primary, #18A058);
|
border-color: var(--jio-color-primary, #18A058);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.medal-icon-picker__preset.is-active {
|
.medal-icon-picker__preset.is-active {
|
||||||
border-color: var(--aksrt-color-primary, #18A058);
|
border-color: var(--jio-color-primary, #18A058);
|
||||||
background: var(--aksrt-color-primary-soft, #EAF7EF);
|
background: var(--jio-color-primary-soft, #EAF7EF);
|
||||||
box-shadow: 0 0 0 1px var(--aksrt-color-primary, #18A058);
|
box-shadow: 0 0 0 1px var(--jio-color-primary, #18A058);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head" v-bind="headAttrs">
|
<div class="jio-sortable__head" v-bind="headAttrs">
|
||||||
<AksSortHandle @click.stop />
|
<JioSortHandle @click.stop />
|
||||||
<span class="aksrt-sortable__icon">
|
<span class="jio-sortable__icon">
|
||||||
<img v-if="item.icon" :src="item.icon" alt="" />
|
<img v-if="item.icon" :src="item.icon" alt="" />
|
||||||
<i v-else class="fa fas fa-medal" aria-hidden="true" />
|
<i v-else class="fa fas fa-medal" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
<div class="aksrt-sortable__main">
|
<div class="jio-sortable__main">
|
||||||
<div class="aksrt-sortable__title-row">
|
<div class="jio-sortable__title-row">
|
||||||
<span class="aksrt-sortable__title">{{ item.name || '未命名徽章' }}</span>
|
<span class="jio-sortable__title">{{ item.name || '未命名徽章' }}</span>
|
||||||
<n-tag size="small" :bordered="false" :type="item.get_type === 'manually_add' ? 'default' : 'info'">
|
<n-tag size="small" :bordered="false" :type="item.get_type === 'manually_add' ? 'default' : 'info'">
|
||||||
{{ conditionSummary(item) }}
|
{{ conditionSummary(item) }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-tag v-if="!item.slug" size="small" type="warning" :bordered="false">缺少 slug</n-tag>
|
<n-tag v-if="!item.slug" size="small" type="warning" :bordered="false">缺少 slug</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<span class="aksrt-sortable__desc">{{ item.slug || '—' }}{{ item.desc ? ` · ${item.desc}` : '' }}</span>
|
<span class="jio-sortable__desc">{{ item.slug || '—' }}{{ item.desc ? ` · ${item.desc}` : '' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-sortable__actions">
|
<div class="jio-sortable__actions">
|
||||||
<n-button size="tiny" quaternary type="error" title="删除条目" @click.stop="emit('remove')">
|
<n-button size="tiny" quaternary type="error" title="删除条目" @click.stop="emit('remove')">
|
||||||
<i class="fa fas fa-trash-alt" aria-hidden="true" />
|
<i class="fa fas fa-trash-alt" aria-hidden="true" />
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<i class="fa fas aksrt-sortable__toggle" :class="open ? 'fa-chevron-down' : 'fa-chevron-right'" aria-hidden="true" />
|
<i class="fa fas jio-sortable__toggle" :class="open ? 'fa-chevron-down' : 'fa-chevron-right'" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="open" class="aksrt-sortable__body">
|
<div v-if="open" class="jio-sortable__body">
|
||||||
<n-form label-placement="left" label-width="96" class="aksrt-form medal-item-card__form">
|
<n-form label-placement="left" label-width="96" class="jio-form medal-item-card__form">
|
||||||
<n-grid cols="1 m:2" :x-gap="16" responsive="screen">
|
<n-grid cols="1 m:2" :x-gap="16" responsive="screen">
|
||||||
<n-form-item-gi label="名称">
|
<n-form-item-gi label="名称">
|
||||||
<n-input v-model:value="item.name" placeholder="如:签到达人" />
|
<n-input v-model:value="item.name" placeholder="如:签到达人" />
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/** 徽章条目卡片:行头摘要(图标 / 名称 / 条件),展开后编辑字段。条目对象由父级直接持有并原地修改。 */
|
/** 徽章条目卡片:行头摘要(图标 / 名称 / 条件),展开后编辑字段。条目对象由父级直接持有并原地修改。 */
|
||||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||||
import { MEDAL_GET_TYPES } from '@/schema/sections/users/badge.js'
|
import { MEDAL_GET_TYPES } from '@/schema/sections/users/badge.js'
|
||||||
import { conditionSummary } from './badge-model.js'
|
import { conditionSummary } from './badge-model.js'
|
||||||
import MedalIconPicker from './MedalIconPicker.vue'
|
import MedalIconPicker from './MedalIconPicker.vue'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>预设回复</h3>
|
<h3>预设回复</h3>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button size="small" :disabled="saving || !reorderable" :loading="savingOrder" @click="saveOrder">
|
<n-button size="small" :disabled="saving || !reorderable" :loading="savingOrder" @click="saveOrder">
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
v-model="rows"
|
v-model="rows"
|
||||||
v-bind="SORTABLE_LIST_ATTRS"
|
v-bind="SORTABLE_LIST_ATTRS"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
class="aksrt-sortable"
|
class="jio-sortable"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<div class="aksrt-sortable__item">
|
<div class="jio-sortable__item">
|
||||||
<div class="aksrt-sortable__head">
|
<div class="jio-sortable__head">
|
||||||
<AksSortHandle />
|
<JioSortHandle />
|
||||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||||
<div class="aksrt-sortable__main">
|
<div class="jio-sortable__main">
|
||||||
<span class="aksrt-ticket-prereply__content">{{ element.content }}</span>
|
<span class="jio-ticket-prereply__content">{{ element.content }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aksrt-sortable__actions">
|
<div class="jio-sortable__actions">
|
||||||
<n-button size="tiny" type="primary" quaternary @click="openEdit(element)">编辑</n-button>
|
<n-button size="tiny" type="primary" quaternary @click="openEdit(element)">编辑</n-button>
|
||||||
<n-button size="tiny" type="error" quaternary @click="handleDelete(element)">删除</n-button>
|
<n-button size="tiny" type="error" quaternary @click="handleDelete(element)">删除</n-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@ import { computed, onMounted, ref } from 'vue'
|
|||||||
import { NButton, NSpace, useDialog, useMessage } from 'naive-ui'
|
import { NButton, NSpace, useDialog, useMessage } from 'naive-ui'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -193,8 +193,8 @@ defineExpose({ reload: load })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.aksrt-ticket-prereply__content {
|
.jio-ticket-prereply__content {
|
||||||
color: var(--aksrt-color-text, #1F2923);
|
color: var(--jio-color-text, #1F2923);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-card class="aksrt-section-card aksrt-ticket-status-panel">
|
<n-card class="jio-section-card jio-ticket-status-panel">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>工单状态</h3>
|
<h3>工单状态</h3>
|
||||||
<n-button size="small" type="primary" :disabled="saving" @click="openCreate">新增状态</n-button>
|
<n-button size="small" type="primary" :disabled="saving" @click="openCreate">新增状态</n-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,13 +24,13 @@
|
|||||||
v-model:show="visible"
|
v-model:show="visible"
|
||||||
:title="editing ? '编辑状态' : '新增状态'"
|
:title="editing ? '编辑状态' : '新增状态'"
|
||||||
preset="card"
|
preset="card"
|
||||||
class="aksrt-status-modal"
|
class="jio-status-modal"
|
||||||
style="width: 460px; max-width: 92vw;"
|
style="width: 460px; max-width: 92vw;"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
>
|
>
|
||||||
<div class="aksrt-status-preview" aria-live="polite">
|
<div class="jio-status-preview" aria-live="polite">
|
||||||
<span class="aksrt-status-preview__label">预览</span>
|
<span class="jio-status-preview__label">预览</span>
|
||||||
<span class="aksrt-ticket-badge" :style="{ '--aksrt-ticket-color': previewColor }">
|
<span class="jio-ticket-badge" :style="{ '--jio-ticket-color': previewColor }">
|
||||||
{{ form.name.trim() || '状态名称' }}
|
{{ form.name.trim() || '状态名称' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,30 +40,30 @@
|
|||||||
<n-input v-model:value="form.name" placeholder="如:处理中" maxlength="60" show-count />
|
<n-input v-model:value="form.name" placeholder="如:处理中" maxlength="60" show-count />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="标识" path="status_key">
|
<n-form-item label="标识" path="status_key">
|
||||||
<div class="aksrt-status-key">
|
<div class="jio-status-key">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="form.status_key"
|
v-model:value="form.status_key"
|
||||||
class="aksrt-status-key__input"
|
class="jio-status-key__input"
|
||||||
:placeholder="editing ? '' : '留空则按名称自动生成'"
|
:placeholder="editing ? '' : '留空则按名称自动生成'"
|
||||||
maxlength="24"
|
maxlength="24"
|
||||||
show-count
|
show-count
|
||||||
:input-props="{ autocomplete: 'off', spellcheck: false }"
|
:input-props="{ autocomplete: 'off', spellcheck: false }"
|
||||||
@blur="form.status_key = normalizeKey(form.status_key)"
|
@blur="form.status_key = normalizeKey(form.status_key)"
|
||||||
/>
|
/>
|
||||||
<span class="aksrt-ticket-status__hint">
|
<span class="jio-ticket-status__hint">
|
||||||
小写字母开头,仅含小写字母、数字与下划线(2–24 位)。修改后,使用该状态的工单会同步改为新标识。
|
小写字母开头,仅含小写字母、数字与下划线(2–24 位)。修改后,使用该状态的工单会同步改为新标识。
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="状态颜色" path="color">
|
<n-form-item label="状态颜色" path="color">
|
||||||
<div class="aksrt-status-color">
|
<div class="jio-status-color">
|
||||||
<div class="aksrt-status-color__swatches" role="radiogroup" aria-label="状态颜色">
|
<div class="jio-status-color__swatches" role="radiogroup" aria-label="状态颜色">
|
||||||
<button
|
<button
|
||||||
v-for="swatch in SWATCHES"
|
v-for="swatch in SWATCHES"
|
||||||
:key="swatch"
|
:key="swatch"
|
||||||
type="button"
|
type="button"
|
||||||
role="radio"
|
role="radio"
|
||||||
class="aksrt-status-color__swatch"
|
class="jio-status-color__swatch"
|
||||||
:class="{ 'is-active': currentHex === swatch }"
|
:class="{ 'is-active': currentHex === swatch }"
|
||||||
:style="{ '--swatch': swatch }"
|
:style="{ '--swatch': swatch }"
|
||||||
:aria-checked="currentHex === swatch"
|
:aria-checked="currentHex === swatch"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
@click="form.color = swatch"
|
@click="form.color = swatch"
|
||||||
/>
|
/>
|
||||||
<!-- 自定义色:未选中时显示色环,选中后显示当前色并带选中环。 -->
|
<!-- 自定义色:未选中时显示色环,选中后显示当前色并带选中环。 -->
|
||||||
<AksColorPicker
|
<JioColorPicker
|
||||||
:model-value="previewColor"
|
:model-value="previewColor"
|
||||||
@update:model-value="setColor"
|
@update:model-value="setColor"
|
||||||
>
|
>
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
:ref="setTriggerRef"
|
:ref="setTriggerRef"
|
||||||
type="button"
|
type="button"
|
||||||
role="radio"
|
role="radio"
|
||||||
class="aksrt-status-color__swatch aksrt-status-color__swatch--custom"
|
class="jio-status-color__swatch jio-status-color__swatch--custom"
|
||||||
:class="{ 'is-active': isCustomColor }"
|
:class="{ 'is-active': isCustomColor }"
|
||||||
:style="{ '--swatch': previewColor }"
|
:style="{ '--swatch': previewColor }"
|
||||||
:aria-checked="isCustomColor"
|
:aria-checked="isCustomColor"
|
||||||
@@ -90,25 +90,25 @@
|
|||||||
@click="onClick"
|
@click="onClick"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</AksColorPicker>
|
</JioColorPicker>
|
||||||
</div>
|
</div>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="form.color"
|
v-model:value="form.color"
|
||||||
class="aksrt-status-color__hex"
|
class="jio-status-color__hex"
|
||||||
maxlength="7"
|
maxlength="7"
|
||||||
placeholder="#6b7280"
|
placeholder="#6b7280"
|
||||||
@blur="form.color = toHex(form.color) || form.color.trim()"
|
@blur="form.color = toHex(form.color) || form.color.trim()"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<i class="aksrt-status-color__chip" :style="{ background: previewColor }" />
|
<i class="jio-status-color__chip" :style="{ background: previewColor }" />
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="完结状态" path="is_closed" :show-feedback="false">
|
<n-form-item label="完结状态" path="is_closed" :show-feedback="false">
|
||||||
<div class="aksrt-status-closed">
|
<div class="jio-status-closed">
|
||||||
<n-switch v-model:value="form.is_closed" />
|
<n-switch v-model:value="form.is_closed" />
|
||||||
<span class="aksrt-ticket-status__hint">开启后该状态视为终态,工单不可再流转</span>
|
<span class="jio-ticket-status__hint">开启后该状态视为终态,工单不可再流转</span>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import { NButton, NSpace, useDialog, useMessage } from 'naive-ui'
|
import { NButton, NSpace, useDialog, useMessage } from 'naive-ui'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import AksColorPicker from '@/components/form/AksColorPicker.vue'
|
import JioColorPicker from '@/components/form/JioColorPicker.vue'
|
||||||
import { toHex } from '@/utils/color.js'
|
import { toHex } from '@/utils/color.js'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -194,7 +194,7 @@ const currentHex = computed(() => toHex(form.value.color))
|
|||||||
const previewColor = computed(() => currentHex.value || DEFAULT_COLOR)
|
const previewColor = computed(() => currentHex.value || DEFAULT_COLOR)
|
||||||
const isCustomColor = computed(() => currentHex.value !== '' && !SWATCHES.includes(currentHex.value))
|
const isCustomColor = computed(() => currentHex.value !== '' && !SWATCHES.includes(currentHex.value))
|
||||||
|
|
||||||
/** AksColorPicker 已归一化为 hex;清空(空串)时保留当前色。 */
|
/** JioColorPicker 已归一化为 hex;清空(空串)时保留当前色。 */
|
||||||
function setColor(value) {
|
function setColor(value) {
|
||||||
const hex = toHex(value)
|
const hex = toHex(value)
|
||||||
if (hex) form.value.color = hex
|
if (hex) form.value.color = hex
|
||||||
@@ -208,15 +208,15 @@ const columns = computed(() => [
|
|||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
// 注意:原生元素用 h('span', ...) 时,文本必须作为 children 直接传入;
|
// 注意:原生元素用 h('span', ...) 时,文本必须作为 children 直接传入;
|
||||||
// 写成 () => row.name 的插槽函数对原生元素不生效,单元格会是空的。
|
// 写成 () => row.name 的插槽函数对原生元素不生效,单元格会是空的。
|
||||||
render: (row) => h('span', { class: 'aksrt-ticket-badge', style: { '--aksrt-ticket-color': row.color } }, row.name),
|
render: (row) => h('span', { class: 'jio-ticket-badge', style: { '--jio-ticket-color': row.color } }, row.name),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '颜色',
|
title: '颜色',
|
||||||
key: 'color',
|
key: 'color',
|
||||||
width: 140,
|
width: 140,
|
||||||
render: (row) => h('span', { class: 'aksrt-ticket-color-cell' }, [
|
render: (row) => h('span', { class: 'jio-ticket-color-cell' }, [
|
||||||
h('i', { class: 'aksrt-ticket-color-dot', style: { background: row.color } }),
|
h('i', { class: 'jio-ticket-color-dot', style: { background: row.color } }),
|
||||||
h('span', { class: 'aksrt-ticket-color-value' }, row.color),
|
h('span', { class: 'jio-ticket-color-value' }, row.color),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -235,7 +235,7 @@ const columns = computed(() => [
|
|||||||
title: '标识',
|
title: '标识',
|
||||||
key: 'status_key',
|
key: 'status_key',
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
render: (row) => h('span', { class: 'aksrt-ticket-key' }, row.status_key),
|
render: (row) => h('span', { class: 'jio-ticket-key' }, row.status_key),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@@ -348,13 +348,13 @@ defineExpose({ reload: load })
|
|||||||
(此前色块 <i> 因此没有尺寸,颜色列只剩色值文本。)
|
(此前色块 <i> 因此没有尺寸,颜色列只剩色值文本。)
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
.aksrt-ticket-status-panel .aksrt-ticket-color-cell {
|
.jio-ticket-status-panel .jio-ticket-color-cell {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-ticket-status-panel .aksrt-ticket-color-dot {
|
.jio-ticket-status-panel .jio-ticket-color-dot {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -363,21 +363,21 @@ defineExpose({ reload: load })
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-ticket-status-panel .aksrt-ticket-color-value,
|
.jio-ticket-status-panel .jio-ticket-color-value,
|
||||||
.aksrt-ticket-status-panel .aksrt-ticket-key {
|
.jio-ticket-status-panel .jio-ticket-key {
|
||||||
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 徽章与前台 assets/css/routes/tickets.css 的 .aksrt-ticket-badge 同构;表格与弹窗预览共用。 */
|
/* 徽章与前台 assets/css/routes/tickets.css 的 .jio-ticket-badge 同构;表格与弹窗预览共用。 */
|
||||||
.aksrt-ticket-status-panel .aksrt-ticket-badge,
|
.jio-ticket-status-panel .jio-ticket-badge,
|
||||||
.aksrt-status-modal .aksrt-ticket-badge {
|
.jio-status-modal .jio-ticket-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: color-mix(in srgb, var(--aksrt-ticket-color, #6b7280) 16%, transparent);
|
background: color-mix(in srgb, var(--jio-ticket-color, #6b7280) 16%, transparent);
|
||||||
color: var(--aksrt-ticket-color, #6b7280);
|
color: var(--jio-ticket-color, #6b7280);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -387,24 +387,24 @@ defineExpose({ reload: load })
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* —— 弹窗:顶部实时预览 —— */
|
/* —— 弹窗:顶部实时预览 —— */
|
||||||
.aksrt-status-preview {
|
.jio-status-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
border: 1px dashed var(--aksrt-color-border);
|
border: 1px dashed var(--jio-color-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: rgba(0, 0, 0, 0.02);
|
background: rgba(0, 0, 0, 0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-preview__label {
|
.jio-status-preview__label {
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-preview .aksrt-ticket-badge {
|
.jio-status-preview .jio-ticket-badge {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -412,20 +412,20 @@ defineExpose({ reload: load })
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* —— 弹窗:颜色选择(预设色板 + 自定义 + 十六进制输入) —— */
|
/* —— 弹窗:颜色选择(预设色板 + 自定义 + 十六进制输入) —— */
|
||||||
.aksrt-status-color {
|
.jio-status-color {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__swatches {
|
.jio-status-color__swatches {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__swatch {
|
.jio-status-color__swatch {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
@@ -438,21 +438,21 @@ defineExpose({ reload: load })
|
|||||||
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__swatch:hover {
|
.jio-status-color__swatch:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__swatch:focus-visible {
|
.jio-status-color__swatch:focus-visible {
|
||||||
outline: 2px solid var(--aksrt-color-primary, #2271b1);
|
outline: 2px solid var(--jio-color-primary, #2271b1);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选中态:白色内圈 + 同色外环,深浅主题都清晰。 */
|
/* 选中态:白色内圈 + 同色外环,深浅主题都清晰。 */
|
||||||
.aksrt-status-color__swatch.is-active {
|
.jio-status-color__swatch.is-active {
|
||||||
box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--swatch);
|
box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--swatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__swatch.is-active::after {
|
.jio-status-color__swatch.is-active::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -465,11 +465,11 @@ defineExpose({ reload: load })
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 自定义:未选中时显示色环,提示「更多颜色」。 */
|
/* 自定义:未选中时显示色环,提示「更多颜色」。 */
|
||||||
.aksrt-status-color__swatch--custom:not(.is-active) {
|
.jio-status-color__swatch--custom:not(.is-active) {
|
||||||
background: conic-gradient(#ef4444, #f59e0b, #eab308, #22c55e, #06b6d4, #3b82f6, #8b5cf6, #ec4899, #ef4444);
|
background: conic-gradient(#ef4444, #f59e0b, #eab308, #22c55e, #06b6d4, #3b82f6, #8b5cf6, #ec4899, #ef4444);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__swatch--custom:not(.is-active)::after {
|
.jio-status-color__swatch--custom:not(.is-active)::after {
|
||||||
content: '+';
|
content: '+';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 5px;
|
inset: 5px;
|
||||||
@@ -484,12 +484,12 @@ defineExpose({ reload: load })
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__hex {
|
.jio-status-color__hex {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-color__chip {
|
.jio-status-color__chip {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
@@ -498,26 +498,26 @@ defineExpose({ reload: load })
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* —— 弹窗:标识 —— */
|
/* —— 弹窗:标识 —— */
|
||||||
.aksrt-status-key {
|
.jio-status-key {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-status-key__input {
|
.jio-status-key__input {
|
||||||
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* —— 弹窗:完结开关 —— */
|
/* —— 弹窗:完结开关 —— */
|
||||||
.aksrt-status-closed {
|
.jio-status-closed {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-ticket-status__hint {
|
.jio-ticket-status__hint {
|
||||||
color: var(--aksrt-color-muted);
|
color: var(--jio-color-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-card class="aksrt-section-card">
|
<n-card class="jio-section-card">
|
||||||
<div class="aksrt-section-header">
|
<div class="jio-section-header">
|
||||||
<h3>工单部门</h3>
|
<h3>工单部门</h3>
|
||||||
<n-button size="small" type="primary" :disabled="saving" @click="openCreate">新增部门</n-button>
|
<n-button size="small" type="primary" :disabled="saving" @click="openCreate">新增部门</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function resolveComponent(page) {
|
|||||||
// Log missing component for debugging — avoids silent fallback to DeferredPage
|
// Log missing component for debugging — avoids silent fallback to DeferredPage
|
||||||
if (page.component && page.component !== 'DeferredPage') {
|
if (page.component && page.component !== 'DeferredPage') {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[AKSRT Route] Component "${page.component}" not found in componentLoaders for route "${page.route}". Falling back to DeferredPage.`
|
`[Jio Route] Component "${page.component}" not found in componentLoaders for route "${page.route}". Falling back to DeferredPage.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,9 +77,9 @@ function createSectionRedirectRoutes() {
|
|||||||
* (封禁/举报 as a sidebar entry lives in constants/sections/users.js → /users/ban.)
|
* (封禁/举报 as a sidebar entry lives in constants/sections/users.js → /users/ban.)
|
||||||
*/
|
*/
|
||||||
const NATIVE_OPS_REDIRECTS = [
|
const NATIVE_OPS_REDIRECTS = [
|
||||||
{ path: '/users/messages', title: '消息中心', nativeAdminPath: 'users.php?page=aksrt_messages' },
|
{ path: '/users/messages', title: '消息中心', nativeAdminPath: 'users.php?page=jio_messages' },
|
||||||
{ path: '/users/report', title: '禁封/举报', nativeAdminPath: 'users.php?page=aksrt_user_ban' },
|
{ path: '/users/report', title: '禁封/举报', nativeAdminPath: 'users.php?page=jio_user_ban' },
|
||||||
{ path: '/users/tickets', title: '工单管理', nativeAdminPath: 'users.php?page=aksrt_tickets' },
|
{ path: '/users/tickets', title: '工单管理', nativeAdminPath: 'users.php?page=jio_tickets' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Schema Registry — central index for all admin section schemas.
|
* Schema Registry — central index for all admin section schemas.
|
||||||
*
|
*
|
||||||
* Each section's schema defines the form fields, groups, API endpoints,
|
* Each section's schema defines the form fields, groups, API endpoints,
|
||||||
* and default values for a settings page. The AksFormRenderer component
|
* and default values for a settings page. The JioFormRenderer component
|
||||||
* consumes these schemas to render settings pages automatically.
|
* consumes these schemas to render settings pages automatically.
|
||||||
*
|
*
|
||||||
* Schema shape per section:
|
* Schema shape per section:
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
export const codeHighlightSchema = {
|
|
||||||
key: 'global.code-highlight',
|
|
||||||
title: '代码高亮',
|
|
||||||
icon: 'fa fas fa-code',
|
|
||||||
description: '代码块语法高亮主题、行号、最大高度等配置。',
|
|
||||||
defaults: {
|
|
||||||
highlight_kg: true,
|
|
||||||
highlight_hh: false,
|
|
||||||
highlight_btn: true,
|
|
||||||
highlight_maxheight: 400,
|
|
||||||
highlight_zt: 'enlighter',
|
|
||||||
highlight_dark_zt: 'monokai',
|
|
||||||
},
|
|
||||||
groups: [
|
|
||||||
{
|
|
||||||
key: 'basic',
|
|
||||||
title: '代码高亮设置',
|
|
||||||
icon: 'fa fas fa-pen',
|
|
||||||
fields: [
|
|
||||||
{ key: 'highlight_kg', label: '代码高亮开关', type: 'switch', default: true },
|
|
||||||
{ key: 'highlight_hh', label: '显示行号', type: 'switch', default: false },
|
|
||||||
{ key: 'highlight_btn', label: '显示扩展按钮', type: 'switch', default: true, help: '控制原始代码 / 信息 / 新窗口按钮的显示' },
|
|
||||||
{ key: 'highlight_maxheight', label: '最大高度(px,0=不限)', type: 'number', default: 400, min: 0, max: 2000, help: '代码块最大高度,超出滚动显示。0 表示不限制' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'theme',
|
|
||||||
title: '主题设置',
|
|
||||||
icon: 'fa fas fa-paint-brush',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'highlight_zt',
|
|
||||||
label: '日间默认主题',
|
|
||||||
type: 'select',
|
|
||||||
default: 'enlighter',
|
|
||||||
options: [
|
|
||||||
{ label: 'Enlighter', value: 'enlighter' },
|
|
||||||
{ label: 'Bootstrap 4', value: 'bootstrap4' },
|
|
||||||
{ label: 'Classic', value: 'classic' },
|
|
||||||
{ label: 'Beyond', value: 'beyond' },
|
|
||||||
{ label: 'Mowtwo', value: 'mowtwo' },
|
|
||||||
{ label: 'Eclipse', value: 'eclipse' },
|
|
||||||
{ label: 'Droide', value: 'droide' },
|
|
||||||
{ label: 'Minimal', value: 'minimal' },
|
|
||||||
{ label: 'Rowhammer', value: 'rowhammer' },
|
|
||||||
{ label: 'Godzilla', value: 'godzilla' },
|
|
||||||
{ label: 'Dracula', value: 'dracula' },
|
|
||||||
{ label: 'Atomic', value: 'atomic' },
|
|
||||||
{ label: 'Monokai', value: 'monokai' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'highlight_dark_zt',
|
|
||||||
label: '暗色默认主题',
|
|
||||||
type: 'select',
|
|
||||||
default: 'monokai',
|
|
||||||
options: [
|
|
||||||
{ label: 'Dracula', value: 'dracula' },
|
|
||||||
{ label: 'Atomic', value: 'atomic' },
|
|
||||||
{ label: 'Monokai', value: 'monokai' },
|
|
||||||
{ label: 'Enlighter', value: 'enlighter' },
|
|
||||||
{ label: 'Bootstrap 4', value: 'bootstrap4' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri
|
const themeRoot = typeof window !== 'undefined' && window.jioAdminData?.themeUri
|
||||||
? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
|
? String(window.jioAdminData.themeUri).replace(/\/$/, '')
|
||||||
: ''
|
: ''
|
||||||
const defaultThumbnail = themeRoot ? `${themeRoot}/assets/img/thumbnail.svg` : ''
|
const defaultThumbnail = themeRoot ? `${themeRoot}/assets/img/thumbnail.svg` : ''
|
||||||
|
|
||||||
|
const noticeColorOptions = [
|
||||||
|
{ label: '主题色', value: 'primary' },
|
||||||
|
{ label: '成功绿', value: 'success' },
|
||||||
|
{ label: '警告橙', value: 'warning' },
|
||||||
|
{ label: '危险红', value: 'danger' },
|
||||||
|
{ label: '信息蓝', value: 'info' },
|
||||||
|
{ label: '深紫', value: 'purple' },
|
||||||
|
{ label: '深灰', value: 'dark' },
|
||||||
|
]
|
||||||
|
|
||||||
export const commonSchema = {
|
export const commonSchema = {
|
||||||
key: 'global.common',
|
key: 'global.common',
|
||||||
title: '常用功能',
|
title: '常用功能',
|
||||||
icon: 'fa fas fa-cog',
|
icon: 'fa fas fa-cog',
|
||||||
description: '管理站点图像懒加载等常用基础配置。',
|
description: '图像懒加载、代码高亮与弹窗公告等全站常用配置。',
|
||||||
defaults: {
|
defaults: {
|
||||||
lazy_posts_thumb: true,
|
lazy_posts_thumb: true,
|
||||||
lazy_avatar: true,
|
lazy_avatar: true,
|
||||||
@@ -18,6 +28,35 @@ export const commonSchema = {
|
|||||||
lazy_other: true,
|
lazy_other: true,
|
||||||
lazy_animation: 'blur',
|
lazy_animation: 'blur',
|
||||||
thumbnail: defaultThumbnail,
|
thumbnail: defaultThumbnail,
|
||||||
|
highlight_kg: true,
|
||||||
|
highlight_hh: false,
|
||||||
|
highlight_btn: true,
|
||||||
|
highlight_maxheight: 400,
|
||||||
|
highlight_zt: 'enlighter',
|
||||||
|
highlight_dark_zt: 'monokai',
|
||||||
|
system_notice_enabled: true,
|
||||||
|
system_notice_policy: '',
|
||||||
|
system_notice_title: '',
|
||||||
|
system_notice_content: '',
|
||||||
|
system_notice_btn1_text: '',
|
||||||
|
system_notice_btn1_link: '',
|
||||||
|
system_notice_btn1_color: 'primary',
|
||||||
|
system_notice_btn2_text: '',
|
||||||
|
system_notice_btn2_link: '',
|
||||||
|
system_notice_btn2_color: 'primary',
|
||||||
|
system_notice_btn3_text: '',
|
||||||
|
system_notice_btn3_link: '',
|
||||||
|
system_notice_btn3_color: 'primary',
|
||||||
|
system_notice_btn4_text: '',
|
||||||
|
system_notice_btn4_link: '',
|
||||||
|
system_notice_btn4_color: 'primary',
|
||||||
|
system_notice_size: '',
|
||||||
|
system_notice_radius: false,
|
||||||
|
system_notice_class: '',
|
||||||
|
system_notice_title_icon: '',
|
||||||
|
system_notice_title_class: '',
|
||||||
|
system_notice_title_style: '',
|
||||||
|
system_notice_expires: 24,
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
@@ -53,5 +92,123 @@ export const commonSchema = {
|
|||||||
{ key: 'lazy_other', label: '其它图片懒加载', type: 'switch', default: true },
|
{ key: 'lazy_other', label: '其它图片懒加载', type: 'switch', default: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'code-highlight',
|
||||||
|
title: '代码高亮',
|
||||||
|
icon: 'fa fas fa-code',
|
||||||
|
fields: [
|
||||||
|
{ key: 'highlight_kg', label: '代码高亮开关', type: 'switch', default: true },
|
||||||
|
{ key: 'highlight_hh', label: '显示行号', type: 'switch', default: false, dependsOn: { key: 'highlight_kg', value: true } },
|
||||||
|
{ key: 'highlight_btn', label: '显示扩展按钮', type: 'switch', default: true, dependsOn: { key: 'highlight_kg', value: true }, help: '控制原始代码 / 信息 / 新窗口按钮的显示' },
|
||||||
|
{ key: 'highlight_maxheight', label: '最大高度(px,0=不限)', type: 'number', default: 400, min: 0, max: 2000, dependsOn: { key: 'highlight_kg', value: true }, help: '代码块最大高度,超出滚动显示。0 表示不限制' },
|
||||||
|
{
|
||||||
|
key: 'highlight_zt',
|
||||||
|
label: '日间默认主题',
|
||||||
|
type: 'select',
|
||||||
|
default: 'enlighter',
|
||||||
|
dependsOn: { key: 'highlight_kg', value: true },
|
||||||
|
options: [
|
||||||
|
{ label: 'Enlighter', value: 'enlighter' },
|
||||||
|
{ label: 'Bootstrap 4', value: 'bootstrap4' },
|
||||||
|
{ label: 'Classic', value: 'classic' },
|
||||||
|
{ label: 'Beyond', value: 'beyond' },
|
||||||
|
{ label: 'Mowtwo', value: 'mowtwo' },
|
||||||
|
{ label: 'Eclipse', value: 'eclipse' },
|
||||||
|
{ label: 'Droide', value: 'droide' },
|
||||||
|
{ label: 'Minimal', value: 'minimal' },
|
||||||
|
{ label: 'Rowhammer', value: 'rowhammer' },
|
||||||
|
{ label: 'Godzilla', value: 'godzilla' },
|
||||||
|
{ label: 'Dracula', value: 'dracula' },
|
||||||
|
{ label: 'Atomic', value: 'atomic' },
|
||||||
|
{ label: 'Monokai', value: 'monokai' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'highlight_dark_zt',
|
||||||
|
label: '暗色默认主题',
|
||||||
|
type: 'select',
|
||||||
|
default: 'monokai',
|
||||||
|
dependsOn: { key: 'highlight_kg', value: true },
|
||||||
|
options: [
|
||||||
|
{ label: 'Dracula', value: 'dracula' },
|
||||||
|
{ label: 'Atomic', value: 'atomic' },
|
||||||
|
{ label: 'Monokai', value: 'monokai' },
|
||||||
|
{ label: 'Enlighter', value: 'enlighter' },
|
||||||
|
{ label: 'Bootstrap 4', value: 'bootstrap4' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system-notice',
|
||||||
|
title: '弹窗通知',
|
||||||
|
icon: 'fa fas fa-bullhorn',
|
||||||
|
fields: [
|
||||||
|
{ key: 'system_notice_enabled', label: '启用系统公告', type: 'switch', default: true },
|
||||||
|
{
|
||||||
|
key: 'system_notice_policy',
|
||||||
|
label: '显示对象',
|
||||||
|
type: 'select',
|
||||||
|
default: '',
|
||||||
|
options: [
|
||||||
|
{ label: '所有访客', value: '' },
|
||||||
|
{ label: '仅登录用户', value: 'signin' },
|
||||||
|
{ label: '仅 VIP 会员', value: 'vip' },
|
||||||
|
{ label: '仅非 VIP', value: 'free' },
|
||||||
|
{ label: '仅认证用户', value: 'auth' },
|
||||||
|
],
|
||||||
|
dependsOn: { key: 'system_notice_enabled', value: true },
|
||||||
|
},
|
||||||
|
{ key: 'system_notice_title', label: '公告标题', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_content', label: '公告内容', type: 'textarea', default: '', rows: 5, dependsOn: { key: 'system_notice_enabled', value: true }, help: '支持 HTML 标签' },
|
||||||
|
{ key: 'system_notice_btn1_text', label: '按钮 1 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn1_link', label: '按钮 1 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true }, help: '可选,填写后按钮变为链接' },
|
||||||
|
{ key: 'system_notice_btn1_color', label: '按钮 1 颜色', type: 'select', default: 'primary', options: noticeColorOptions, dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn2_text', label: '按钮 2 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn2_link', label: '按钮 2 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn2_color', label: '按钮 2 颜色', type: 'select', default: 'primary', options: noticeColorOptions, dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn3_text', label: '按钮 3 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn3_link', label: '按钮 3 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn3_color', label: '按钮 3 颜色', type: 'select', default: 'primary', options: noticeColorOptions, dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn4_text', label: '按钮 4 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn4_link', label: '按钮 4 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_btn4_color', label: '按钮 4 颜色', type: 'select', default: 'primary', options: noticeColorOptions, dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{
|
||||||
|
key: 'system_notice_size',
|
||||||
|
label: '弹窗尺寸',
|
||||||
|
type: 'select',
|
||||||
|
default: '',
|
||||||
|
options: [
|
||||||
|
{ label: '默认', value: '' },
|
||||||
|
{ label: '迷你', value: 'modal-mini' },
|
||||||
|
{ label: '小号', value: 'modal-sm' },
|
||||||
|
{ label: '大号', value: 'modal-lg' },
|
||||||
|
],
|
||||||
|
dependsOn: { key: 'system_notice_enabled', value: true },
|
||||||
|
},
|
||||||
|
{ key: 'system_notice_radius', label: '按钮圆角', type: 'switch', default: false, dependsOn: { key: 'system_notice_enabled', value: true }, help: '开启后弹窗按钮使用圆角样式' },
|
||||||
|
{ key: 'system_notice_class', label: '公告样式类', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_title_icon', label: '标题图标', type: 'icon', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{
|
||||||
|
key: 'system_notice_title_class',
|
||||||
|
label: '标题背景配色',
|
||||||
|
type: 'select',
|
||||||
|
default: '',
|
||||||
|
dependsOn: { key: 'system_notice_enabled', value: true },
|
||||||
|
options: [
|
||||||
|
{ label: '无', value: '' },
|
||||||
|
{ label: '炫彩黄', value: 'jb-yellow' },
|
||||||
|
{ label: '炫彩蓝', value: 'jb-blue' },
|
||||||
|
{ label: '炫彩绿', value: 'jb-green' },
|
||||||
|
{ label: '炫彩紫', value: 'jb-purple' },
|
||||||
|
{ label: '炫彩红', value: 'jb-red' },
|
||||||
|
{ label: '炫彩橙', value: 'jb-orange' },
|
||||||
|
],
|
||||||
|
help: '弹窗标题区域的背景配色方案',
|
||||||
|
},
|
||||||
|
{ key: 'system_notice_title_style', label: '标题样式', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
||||||
|
{ key: 'system_notice_expires', label: '弹窗周期(小时)', type: 'number', default: 24, min: 0, max: 720, dependsOn: { key: 'system_notice_enabled', value: true }, help: '0 表示永不过期' },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ export const i18nSchema = {
|
|||||||
],
|
],
|
||||||
dependsOn: { key: 'i18n_enabled', value: true },
|
dependsOn: { key: 'i18n_enabled', value: true },
|
||||||
},
|
},
|
||||||
{ key: 'i18n_auto_detect', label: '自动检测浏览器语言', type: 'switch', default: true, dependsOn: { key: 'i18n_enabled', value: true }, help: '基于浏览器 Accept-Language 自动切换(AKSRT 方案,区别于 legacy IP 定位方案)' },
|
{ key: 'i18n_auto_detect', label: '自动检测浏览器语言', type: 'switch', default: true, dependsOn: { key: 'i18n_enabled', value: true }, help: '基于浏览器 Accept-Language 自动切换(Jio 方案,区别于 legacy IP 定位方案)' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import { tabbarSchema } from './tabbar.js'
|
|||||||
import { i18nSchema } from './i18n.js'
|
import { i18nSchema } from './i18n.js'
|
||||||
import { customCodeSchema } from './custom-code.js'
|
import { customCodeSchema } from './custom-code.js'
|
||||||
import { seoSchema } from './seo.js'
|
import { seoSchema } from './seo.js'
|
||||||
import { codeHighlightSchema } from './code-highlight.js'
|
|
||||||
import { systemNoticeSchema } from './system-notice.js'
|
|
||||||
import { shareSchema } from './share.js'
|
import { shareSchema } from './share.js'
|
||||||
|
|
||||||
export const globalSchemas = {
|
export const globalSchemas = {
|
||||||
@@ -26,7 +24,5 @@ export const globalSchemas = {
|
|||||||
i18n: i18nSchema,
|
i18n: i18nSchema,
|
||||||
'custom-code': customCodeSchema,
|
'custom-code': customCodeSchema,
|
||||||
seo: seoSchema,
|
seo: seoSchema,
|
||||||
'code-highlight': codeHighlightSchema,
|
|
||||||
'system-notice': systemNoticeSchema,
|
|
||||||
share: shareSchema,
|
share: shareSchema,
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ export const layoutSchema = {
|
|||||||
theme_skin: 'f04494',
|
theme_skin: 'f04494',
|
||||||
dark_theme_skin_custom: '',
|
dark_theme_skin_custom: '',
|
||||||
close_backdrop: false,
|
close_backdrop: false,
|
||||||
theme_main_radius: 12,
|
theme_main_radius: 8,
|
||||||
container_max_width: 1200,
|
container_max_width: 1200,
|
||||||
sidebar_enabled: true,
|
sidebar_enabled: true,
|
||||||
sidebar_position: 'right',
|
sidebar_position: 'right',
|
||||||
@@ -147,7 +147,7 @@ export const layoutSchema = {
|
|||||||
},
|
},
|
||||||
{ key: 'dark_theme_skin_custom', label: '夜间模式主题色', type: 'color', default: '', help: '在此可单独设置夜间模式的高亮色,清空此处则使用上方默认高亮色' },
|
{ key: 'dark_theme_skin_custom', label: '夜间模式主题色', type: 'color', default: '', help: '在此可单独设置夜间模式的高亮色,清空此处则使用上方默认高亮色' },
|
||||||
{ key: 'close_backdrop', label: '全局关闭背景高斯模糊', type: 'switch', default: false, help: '在支持的浏览器上部分UI(例如导航栏)会显示背景高斯模糊的特效,但此功能比较消耗浏览器性能。同时系统会自动检测用户浏览器性能情况,如果性能较差,将自动关闭此特效,以提供更流畅的体验' },
|
{ key: 'close_backdrop', label: '全局关闭背景高斯模糊', type: 'switch', default: false, help: '在支持的浏览器上部分UI(例如导航栏)会显示背景高斯模糊的特效,但此功能比较消耗浏览器性能。同时系统会自动检测用户浏览器性能情况,如果性能较差,将自动关闭此特效,以提供更流畅的体验' },
|
||||||
{ key: 'theme_main_radius', label: '卡片圆角', type: 'spinner', default: 12, min: 0, max: 30, step: 1, unit: 'PX', description: '页面卡片的圆角尺寸' },
|
{ key: 'theme_main_radius', label: '卡片圆角', type: 'spinner', default: 8, min: 0, max: 30, step: 1, unit: 'PX', description: '页面卡片的圆角尺寸' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
const themeRoot = typeof window !== 'undefined' && window.jioAdminData?.themeUri
|
||||||
|
? String(window.jioAdminData.themeUri).replace(/\/$/, '')
|
||||||
|
: ''
|
||||||
|
const defaultLogo = themeRoot ? `${themeRoot}/assets/img/logo.png` : ''
|
||||||
|
const defaultFavicon = themeRoot ? `${themeRoot}/assets/img/favicon.png` : ''
|
||||||
|
|
||||||
export const logoSchema = {
|
export const logoSchema = {
|
||||||
key: 'global.logo',
|
key: 'global.logo',
|
||||||
title: 'LOGO & 品牌',
|
title: 'LOGO & 品牌',
|
||||||
icon: 'fa fas fa-image',
|
icon: 'fa fas fa-image',
|
||||||
description: '管理站点 Logo、站点图标和品牌标识。',
|
description: '管理站点 Logo、站点图标和品牌标识。',
|
||||||
defaults: {
|
defaults: {
|
||||||
site_logo: '',
|
site_logo: defaultLogo,
|
||||||
site_logo_dark: '',
|
site_logo_dark: defaultLogo,
|
||||||
site_favicon: '',
|
site_favicon: defaultFavicon,
|
||||||
site_icon: '',
|
site_icon: defaultFavicon,
|
||||||
logo_width: 150,
|
|
||||||
logo_text: '',
|
logo_text: '',
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
@@ -17,9 +22,8 @@ export const logoSchema = {
|
|||||||
title: 'Logo 设置',
|
title: 'Logo 设置',
|
||||||
icon: 'fa fas fa-image',
|
icon: 'fa fas fa-image',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'site_logo', label: '站点 Logo', type: 'media', help: '建议尺寸 300x80,支持 PNG/SVG 透明背景' },
|
{ key: 'site_logo', label: '站点 Logo', type: 'media', default: defaultLogo, help: '建议尺寸 300x80,支持 PNG/SVG 透明背景' },
|
||||||
{ key: 'site_logo_dark', label: '暗色模式 Logo', type: 'media', help: '暗色模式下显示的 Logo,留空则使用默认 Logo' },
|
{ key: 'site_logo_dark', label: '暗色模式 Logo', type: 'media', default: defaultLogo, help: '暗色模式下显示的 Logo,留空则使用默认 Logo' },
|
||||||
{ key: 'logo_width', label: 'Logo 宽度 (px)', type: 'number', min: 50, max: 400, step: 10, help: '导航栏中 Logo 显示宽度' },
|
|
||||||
{ key: 'logo_text', label: 'Logo 文字', type: 'input', placeholder: '当无 Logo 图片时显示的文字', help: '同时作为首页标题和 alt 文本使用' },
|
{ key: 'logo_text', label: 'Logo 文字', type: 'input', placeholder: '当无 Logo 图片时显示的文字', help: '同时作为首页标题和 alt 文本使用' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -28,8 +32,8 @@ export const logoSchema = {
|
|||||||
title: '站点图标',
|
title: '站点图标',
|
||||||
icon: 'fa fas fa-id-badge',
|
icon: 'fa fas fa-id-badge',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'site_favicon', label: '浏览器图标 (Favicon)', type: 'media', help: '建议 32x32 或 16x16 的 ICO/PNG 文件' },
|
{ key: 'site_favicon', label: '浏览器图标 (Favicon)', type: 'media', default: defaultFavicon, help: '建议 32x32 或 16x16 的 ICO/PNG 文件' },
|
||||||
{ key: 'site_icon', label: '移动端图标', type: 'media', help: '建议 192x192 的 PNG,用于移动端添加到桌面' },
|
{ key: 'site_icon', label: '移动端图标', type: 'media', default: defaultFavicon, help: '建议 192x192 的 PNG,用于移动端添加到桌面' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const searchSchema = {
|
|||||||
search_custom_key: ['title', 'content', 'excerpt', 'category', 'post_tag'],
|
search_custom_key: ['title', 'content', 'excerpt', 'category', 'post_tag'],
|
||||||
search_facets: {},
|
search_facets: {},
|
||||||
search_ms_s: false,
|
search_ms_s: false,
|
||||||
search_ms_opt: { api_url: 'http://127.0.0.1:7700', api_key: '', index_prefix: 'aksrt', synonyms: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }] },
|
search_ms_opt: { api_url: 'http://127.0.0.1:7700', api_key: '', index_prefix: 'jio', synonyms: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }] },
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
@@ -63,12 +63,13 @@ export const searchSchema = {
|
|||||||
{ key: 'search_custom_key_s', label: '自定义匹配范围', type: 'switch', default: true, help: '启用后可配置搜索匹配字段范围' },
|
{ key: 'search_custom_key_s', label: '自定义匹配范围', type: 'switch', default: true, help: '启用后可配置搜索匹配字段范围' },
|
||||||
{ key: 'search_custom_key', label: '匹配字段', type: 'checkbox', default: ['title', 'content', 'excerpt', 'category', 'post_tag'], dependsOn: { key: 'search_custom_key_s', value: true }, options: [
|
{ key: 'search_custom_key', label: '匹配字段', type: 'checkbox', default: ['title', 'content', 'excerpt', 'category', 'post_tag'], dependsOn: { key: 'search_custom_key_s', value: true }, options: [
|
||||||
{ label: '标题', value: 'title' }, { label: '内容', value: 'content' }, { label: '简介', value: 'excerpt' },
|
{ label: '标题', value: 'title' }, { label: '内容', value: 'content' }, { label: '简介', value: 'excerpt' },
|
||||||
{ label: '分类', value: 'category' }, { label: '标签', value: 'post_tag' },
|
{ label: '分类', value: 'category' }, { label: '标签', value: 'post_tag' }, { label: '专题', value: 'topics' },
|
||||||
{ label: '评论', value: 'coment' }, { label: '作者昵称', value: 'user' },
|
{ label: '评论', value: 'coment' }, { label: '作者昵称', value: 'user' },
|
||||||
] },
|
] },
|
||||||
{ key: 'search_facets', label: '搜索筛选', type: 'fieldset', sanitizeType: 'array', default: {}, dependsOn: { key: 'search_ms_s', value: false }, help: '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', fields: [
|
{ key: 'search_facets', label: '搜索筛选', type: 'fieldset', sanitizeType: 'array', default: {}, dependsOn: { key: 'search_ms_s', value: false }, help: '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', fields: [
|
||||||
{ key: 'category', label: '[文章] 分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [] },
|
{ key: 'category', label: '[文章] 分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [] },
|
||||||
{ key: 'post_tag', label: '[文章] 标签', type: 'taxonomy_select', taxonomy: 'post_tag', multiple: true, default: [] },
|
{ key: 'post_tag', label: '[文章] 标签', type: 'taxonomy_select', taxonomy: 'post_tag', multiple: true, default: [] },
|
||||||
|
{ key: 'topics', label: '[文章] 专题', type: 'taxonomy_select', taxonomy: 'topics', multiple: true, default: [] },
|
||||||
] },
|
] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -98,10 +99,10 @@ export const searchSchema = {
|
|||||||
icon: 'fa fas fa-bolt',
|
icon: 'fa fas fa-bolt',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'search_ms_s', label: '启用 Meilisearch', type: 'switch', default: false },
|
{ key: 'search_ms_s', label: '启用 Meilisearch', type: 'switch', default: false },
|
||||||
{ key: 'search_ms_opt', label: 'Meilisearch 参数', type: 'fieldset', sanitizeType: 'array', dependsOn: { key: 'search_ms_s', value: true }, default: { api_url: 'http://127.0.0.1:7700', api_key: '', index_prefix: 'aksrt', synonyms: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }] }, fields: [
|
{ key: 'search_ms_opt', label: 'Meilisearch 参数', type: 'fieldset', sanitizeType: 'array', dependsOn: { key: 'search_ms_s', value: true }, default: { api_url: 'http://127.0.0.1:7700', api_key: '', index_prefix: 'jio', synonyms: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }] }, fields: [
|
||||||
{ key: 'api_url', label: 'API 地址', type: 'input', default: 'http://127.0.0.1:7700' },
|
{ key: 'api_url', label: 'API 地址', type: 'input', default: 'http://127.0.0.1:7700' },
|
||||||
{ key: 'api_key', label: 'API 密钥', type: 'input', default: '' },
|
{ key: 'api_key', label: 'API 密钥', type: 'input', default: '' },
|
||||||
{ key: 'index_prefix', label: '索引前缀', type: 'input', default: 'aksrt' },
|
{ key: 'index_prefix', label: '索引前缀', type: 'input', default: 'jio' },
|
||||||
{ key: 'synonyms', label: '同义词', type: 'repeater', default: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }], addText: '添加同义词', fields: [
|
{ key: 'synonyms', label: '同义词', type: 'repeater', default: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }], addText: '添加同义词', fields: [
|
||||||
{ key: 'key', label: '关键词', type: 'input', default: '' },
|
{ key: 'key', label: '关键词', type: 'input', default: '' },
|
||||||
{ key: 'val', label: '同义词', type: 'textarea', default: '' },
|
{ key: 'val', label: '同义词', type: 'textarea', default: '' },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri
|
const themeRoot = typeof window !== 'undefined' && window.jioAdminData?.themeUri
|
||||||
? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
|
? String(window.jioAdminData.themeUri).replace(/\/$/, '')
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
const shareOptions = [
|
const shareOptions = [
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
export const systemNoticeSchema = {
|
|
||||||
key: 'global.system-notice',
|
|
||||||
title: '弹窗通知',
|
|
||||||
icon: 'fa fas fa-bullhorn',
|
|
||||||
description: '全站弹窗公告配置,支持按用户身份定向、多按钮组、弹窗尺寸与过期周期。',
|
|
||||||
defaults: {
|
|
||||||
system_notice_enabled: true,
|
|
||||||
system_notice_policy: '',
|
|
||||||
system_notice_title: '',
|
|
||||||
system_notice_content: '',
|
|
||||||
system_notice_btn1_text: '',
|
|
||||||
system_notice_btn1_link: '',
|
|
||||||
system_notice_btn1_color: 'primary',
|
|
||||||
system_notice_btn2_text: '',
|
|
||||||
system_notice_btn2_link: '',
|
|
||||||
system_notice_btn2_color: 'primary',
|
|
||||||
system_notice_btn3_text: '',
|
|
||||||
system_notice_btn3_link: '',
|
|
||||||
system_notice_btn3_color: 'primary',
|
|
||||||
system_notice_btn4_text: '',
|
|
||||||
system_notice_btn4_link: '',
|
|
||||||
system_notice_btn4_color: 'primary',
|
|
||||||
system_notice_size: '',
|
|
||||||
system_notice_radius: false,
|
|
||||||
system_notice_class: '',
|
|
||||||
system_notice_title_icon: '',
|
|
||||||
system_notice_title_class: '',
|
|
||||||
system_notice_title_style: '',
|
|
||||||
system_notice_expires: 24,
|
|
||||||
},
|
|
||||||
groups: [
|
|
||||||
{
|
|
||||||
key: 'basic',
|
|
||||||
title: '公告设置',
|
|
||||||
icon: 'fa fas fa-bullhorn',
|
|
||||||
fields: [
|
|
||||||
{ key: 'system_notice_enabled', label: '启用系统公告', type: 'switch', default: true },
|
|
||||||
{
|
|
||||||
key: 'system_notice_policy',
|
|
||||||
label: '显示对象',
|
|
||||||
type: 'select',
|
|
||||||
default: '',
|
|
||||||
options: [
|
|
||||||
{ label: '所有访客', value: '' },
|
|
||||||
{ label: '仅登录用户', value: 'signin' },
|
|
||||||
{ label: '仅 VIP 会员', value: 'vip' },
|
|
||||||
{ label: '仅非 VIP', value: 'free' },
|
|
||||||
{ label: '仅认证用户', value: 'auth' },
|
|
||||||
],
|
|
||||||
dependsOn: { key: 'system_notice_enabled', value: true },
|
|
||||||
},
|
|
||||||
{ key: 'system_notice_title', label: '公告标题', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_content', label: '公告内容', type: 'textarea', default: '', rows: 5, dependsOn: { key: 'system_notice_enabled', value: true }, help: '支持 HTML 标签' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'buttons',
|
|
||||||
title: '按钮组(留空则不显示)',
|
|
||||||
icon: 'fa fas fa-th-large',
|
|
||||||
fields: [
|
|
||||||
{ key: 'system_notice_btn1_text', label: '按钮 1 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn1_link', label: '按钮 1 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true }, help: '可选,填写后按钮变为链接' },
|
|
||||||
{ key: 'system_notice_btn1_color', label: '按钮 1 颜色', type: 'select', default: 'primary', options: [
|
|
||||||
{ label: '主题色', value: 'primary' },
|
|
||||||
{ label: '成功绿', value: 'success' },
|
|
||||||
{ label: '警告橙', value: 'warning' },
|
|
||||||
{ label: '危险红', value: 'danger' },
|
|
||||||
{ label: '信息蓝', value: 'info' },
|
|
||||||
{ label: '深紫', value: 'purple' },
|
|
||||||
{ label: '深灰', value: 'dark' },
|
|
||||||
], dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn2_text', label: '按钮 2 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn2_link', label: '按钮 2 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn2_color', label: '按钮 2 颜色', type: 'select', default: 'primary', options: [
|
|
||||||
{ label: '主题色', value: 'primary' },
|
|
||||||
{ label: '成功绿', value: 'success' },
|
|
||||||
{ label: '警告橙', value: 'warning' },
|
|
||||||
{ label: '危险红', value: 'danger' },
|
|
||||||
{ label: '信息蓝', value: 'info' },
|
|
||||||
{ label: '深紫', value: 'purple' },
|
|
||||||
{ label: '深灰', value: 'dark' },
|
|
||||||
], dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn3_text', label: '按钮 3 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn3_link', label: '按钮 3 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn3_color', label: '按钮 3 颜色', type: 'select', default: 'primary', options: [
|
|
||||||
{ label: '主题色', value: 'primary' },
|
|
||||||
{ label: '成功绿', value: 'success' },
|
|
||||||
{ label: '警告橙', value: 'warning' },
|
|
||||||
{ label: '危险红', value: 'danger' },
|
|
||||||
{ label: '信息蓝', value: 'info' },
|
|
||||||
{ label: '深紫', value: 'purple' },
|
|
||||||
{ label: '深灰', value: 'dark' },
|
|
||||||
], dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn4_text', label: '按钮 4 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn4_link', label: '按钮 4 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_btn4_color', label: '按钮 4 颜色', type: 'select', default: 'primary', options: [
|
|
||||||
{ label: '主题色', value: 'primary' },
|
|
||||||
{ label: '成功绿', value: 'success' },
|
|
||||||
{ label: '警告橙', value: 'warning' },
|
|
||||||
{ label: '危险红', value: 'danger' },
|
|
||||||
{ label: '信息蓝', value: 'info' },
|
|
||||||
{ label: '深紫', value: 'purple' },
|
|
||||||
{ label: '深灰', value: 'dark' },
|
|
||||||
], dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'style',
|
|
||||||
title: '样式设置',
|
|
||||||
icon: 'fa fas fa-paint-brush',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'system_notice_size',
|
|
||||||
label: '弹窗尺寸',
|
|
||||||
type: 'select',
|
|
||||||
default: '',
|
|
||||||
options: [
|
|
||||||
{ label: '默认', value: '' },
|
|
||||||
{ label: '迷你', value: 'modal-mini' },
|
|
||||||
{ label: '小号', value: 'modal-sm' },
|
|
||||||
{ label: '大号', value: 'modal-lg' },
|
|
||||||
],
|
|
||||||
dependsOn: { key: 'system_notice_enabled', value: true },
|
|
||||||
},
|
|
||||||
{ key: 'system_notice_radius', label: '按钮圆角', type: 'switch', default: false, dependsOn: { key: 'system_notice_enabled', value: true }, help: '开启后弹窗按钮使用圆角样式' },
|
|
||||||
{ key: 'system_notice_class', label: '公告样式类', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_title_icon', label: '标题图标', type: 'icon', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
{ key: 'system_notice_title_class', label: '标题背景配色', type: 'select', default: '', dependsOn: { key: 'system_notice_enabled', value: true }, options: [
|
|
||||||
{ label: '无', value: '' },
|
|
||||||
{ label: '炫彩黄', value: 'jb-yellow' },
|
|
||||||
{ label: '炫彩蓝', value: 'jb-blue' },
|
|
||||||
{ label: '炫彩绿', value: 'jb-green' },
|
|
||||||
{ label: '炫彩紫', value: 'jb-purple' },
|
|
||||||
{ label: '炫彩红', value: 'jb-red' },
|
|
||||||
{ label: '炫彩橙', value: 'jb-orange' },
|
|
||||||
], help: '弹窗标题区域的背景配色方案' },
|
|
||||||
{ key: 'system_notice_title_style', label: '标题样式', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'expire',
|
|
||||||
title: '过期设置',
|
|
||||||
icon: 'fa fas fa-clock',
|
|
||||||
fields: [
|
|
||||||
{ key: 'system_notice_expires', label: '弹窗周期(小时)', type: 'number', default: 24, min: 0, max: 720, dependsOn: { key: 'system_notice_enabled', value: true }, help: '0 表示永不过期' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -93,7 +93,7 @@ export const gatewaySchema = {
|
|||||||
label: '',
|
label: '',
|
||||||
type: 'content',
|
type: 'content',
|
||||||
ui_only: true,
|
ui_only: true,
|
||||||
content: '<ul><li>以下收款接口,主题仅提供API接入服务,收款平台的可靠性请自行斟酌!</li><li>涉及到资金及信息安全,有相关执照的商家推荐使用官方接口</li><li>微信 / 支付宝接口可在「官方」与「易支付」之间选择;PayPal 与 Stripe 独立开关</li><li>异步回调由各网关验签,无需额外口令;回调地址固定为 /wp-json/aksrt/v1/payment/{gateway}/{notify|return}</li></ul>',
|
content: '<ul><li>以下收款接口,主题仅提供API接入服务,收款平台的可靠性请自行斟酌!</li><li>涉及到资金及信息安全,有相关执照的商家推荐使用官方接口</li><li>微信 / 支付宝接口可在「官方」与「易支付」之间选择;PayPal 与 Stripe 独立开关</li><li>异步回调由各网关验签,无需额外口令;回调地址固定为 /wp-json/jio/v1/payment/{gateway}/{notify|return}</li></ul>',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -159,7 +159,7 @@ export const gatewaySchema = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ key: 'pay_sdk_custom_title_s', label: '自定义收款标题', type: 'switch', default: false, help: '默认为付款类型加网站名,如需自定义请开启' },
|
{ key: 'pay_sdk_custom_title_s', label: '自定义收款标题', type: 'switch', default: false, help: '默认为付款类型加网站名,如需自定义请开启' },
|
||||||
{ key: 'pay_sdk_custom_title', label: '自定义收款标题', type: 'input', default: '', dependsOn: { key: 'pay_sdk_custom_title_s', value: true }, help: '可使用变量:订单类型 %order_type%,例如:%order_type%-AKSRT;注意控制总长度,太长会导致部分接口报错' },
|
{ key: 'pay_sdk_custom_title', label: '自定义收款标题', type: 'input', default: '', dependsOn: { key: 'pay_sdk_custom_title_s', value: true }, help: '可使用变量:订单类型 %order_type%,例如:%order_type%-Jio;注意控制总长度,太长会导致部分接口报错' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -208,7 +208,7 @@ export const gatewaySchema = {
|
|||||||
label: '',
|
label: '',
|
||||||
type: 'content',
|
type: 'content',
|
||||||
ui_only: true,
|
ui_only: true,
|
||||||
content: '<ul><li>支付宝公钥为必填项目(公钥错误可以成功付款,但会回调失败)</li><li>回调地址固定为 /wp-json/aksrt/v1/payment/alipay/{notify|return}</li><li>电脑端按「电脑端收款」选择站内二维码或跳转;手机端开启 H5 时跳转,否则显示二维码</li></ul>',
|
content: '<ul><li>支付宝公钥为必填项目(公钥错误可以成功付款,但会回调失败)</li><li>回调地址固定为 /wp-json/jio/v1/payment/alipay/{notify|return}</li><li>电脑端按「电脑端收款」选择站内二维码或跳转;手机端开启 H5 时跳转,否则显示二维码</li></ul>',
|
||||||
},
|
},
|
||||||
{ key: 'alipay_appid', label: '当面付:APPID', type: 'input', default: '' },
|
{ key: 'alipay_appid', label: '当面付:APPID', type: 'input', default: '' },
|
||||||
{ key: 'alipay_privatekey', label: '当面付:应用私钥', type: 'textarea', sanitize_type: 'code', default: '', rows: 4, secret: true },
|
{ key: 'alipay_privatekey', label: '当面付:应用私钥', type: 'textarea', sanitize_type: 'code', default: '', rows: 4, secret: true },
|
||||||
@@ -243,7 +243,7 @@ export const gatewaySchema = {
|
|||||||
label: '',
|
label: '',
|
||||||
type: 'content',
|
type: 'content',
|
||||||
ui_only: true,
|
ui_only: true,
|
||||||
content: '<ul><li>微信企业支付:官方接口,商家可申请,有年费,申请较为复杂</li><li>回调地址固定为 /wp-json/aksrt/v1/payment/wechatpay/{notify|return},请配置 Native 支付回调链接</li><li>电脑端显示站内二维码;手机端按下方开关选择 H5 跳转或微信内 JSAPI,未开启时同样显示二维码</li></ul>',
|
content: '<ul><li>微信企业支付:官方接口,商家可申请,有年费,申请较为复杂</li><li>回调地址固定为 /wp-json/jio/v1/payment/wechatpay/{notify|return},请配置 Native 支付回调链接</li><li>电脑端显示站内二维码;手机端按下方开关选择 H5 跳转或微信内 JSAPI,未开启时同样显示二维码</li></ul>',
|
||||||
},
|
},
|
||||||
{ key: 'wechat_merchantid', label: '商户号 PartnerID', type: 'input', default: '' },
|
{ key: 'wechat_merchantid', label: '商户号 PartnerID', type: 'input', default: '' },
|
||||||
{ key: 'wechat_appid', label: '授权绑定的AppID', type: 'input', default: '' },
|
{ key: 'wechat_appid', label: '授权绑定的AppID', type: 'input', default: '' },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 知识付费 — 1:1 复刻参考主题对应页面(免登陆购买 / 模块位置 / 优惠码 /
|
* 知识付费 — 1:1 复刻参考主题对应页面(免登陆购买 / 模块位置 / 优惠码 /
|
||||||
* 客户服务 / 付费下载 / 商品预置参数),并保留 AKSRT 订单展示与状态文案。
|
* 客户服务 / 付费下载 / 商品预置参数),并保留 Jio 订单展示与状态文案。
|
||||||
*/
|
*/
|
||||||
export const knowledgeSchema = {
|
export const knowledgeSchema = {
|
||||||
key: 'payment.knowledge',
|
key: 'payment.knowledge',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* VIP会员 — 结构 1:1 对照参考主题 Zibll:会员等级(开关)、会员权益
|
* VIP会员 — 结构 1:1 对照既有实现:会员等级(开关)、会员权益
|
||||||
* (普通/一级/二级 tab)、会员购买选项(一级/二级/续费/升级 tab)、
|
* (普通/一级/二级 tab)、会员购买选项(一级/二级/续费/升级 tab)、
|
||||||
* 积分兑换会员与界面文案。
|
* 积分兑换会员与界面文案。
|
||||||
*
|
*
|
||||||
@@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
const VIP_EQUITY_DEFAULT = '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>';
|
const VIP_EQUITY_DEFAULT = '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>';
|
||||||
|
|
||||||
|
// 主题自带会员图标(assets/img/user/vip/vip-N.svg)。
|
||||||
|
const themeRoot = typeof window !== 'undefined' && window.jioAdminData?.themeUri
|
||||||
|
? String(window.jioAdminData.themeUri).replace(/\/$/, '')
|
||||||
|
: ''
|
||||||
|
const vipIconDefault = (level) => `${themeRoot}/assets/img/user/vip/vip-${level}.svg`
|
||||||
|
|
||||||
const vipTagPalette = [
|
const vipTagPalette = [
|
||||||
{ label: '#f74b3d', value: 'b-red' },
|
{ label: '#f74b3d', value: 'b-red' },
|
||||||
{ label: '#f3920a', value: 'b-yellow' },
|
{ label: '#f3920a', value: 'b-yellow' },
|
||||||
@@ -34,7 +40,7 @@ const vipUnitOptions = [
|
|||||||
{ label: '月', value: 'month' },
|
{ label: '月', value: 'month' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 会员商品/续费商品:标题行显示价格与时长摘要(行默认收起由 AksField 统一处理)。
|
// 会员商品/续费商品:标题行显示价格与时长摘要(行默认收起由 JioField 统一处理)。
|
||||||
const vipProductGroupOptions = {
|
const vipProductGroupOptions = {
|
||||||
listPreview: { prefix: '价格:', titleKey: 'price', descKey: 'time', descUnitKey: 'unit' },
|
listPreview: { prefix: '价格:', titleKey: 'price', descKey: 'time', descUnitKey: 'unit' },
|
||||||
};
|
};
|
||||||
@@ -84,7 +90,7 @@ function vipLevelTabFields(level) {
|
|||||||
help: '会员购买价格配置:执行价、原价、促销标签、标签颜色与会员有效时间',
|
help: '会员购买价格配置:执行价、原价、促销标签、标签颜色与会员有效时间',
|
||||||
fields: vipProductFields(),
|
fields: vipProductFields(),
|
||||||
},
|
},
|
||||||
{ key: `vip_${level}_img_icon`, label: '会员图标', type: 'upload', default: '', dependsOn: dep, help: `留空使用内置 vip-${level}.svg 图标;建议尺寸300x300` },
|
{ key: `vip_${level}_img_icon`, label: '会员图标', type: 'upload', default: vipIconDefault(level), dependsOn: dep, help: '默认使用主题自带会员图标;建议尺寸300x300' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +111,7 @@ export const vipSchema = {
|
|||||||
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||||
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||||
],
|
],
|
||||||
vip_1_img_icon: '',
|
vip_1_img_icon: vipIconDefault(1),
|
||||||
pay_user_vip_2_s: true,
|
pay_user_vip_2_s: true,
|
||||||
pay_user_vip_2_name: '钻石会员',
|
pay_user_vip_2_name: '钻石会员',
|
||||||
pay_user_vip_2_desc: '尊享专属特权',
|
pay_user_vip_2_desc: '尊享专属特权',
|
||||||
@@ -115,7 +121,7 @@ export const vipSchema = {
|
|||||||
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||||
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||||
],
|
],
|
||||||
vip_2_img_icon: '',
|
vip_2_img_icon: vipIconDefault(2),
|
||||||
pay_user_vip_desc: '开通VIP会员,享受会员专属折扣以及多项特权',
|
pay_user_vip_desc: '开通VIP会员,享受会员专属折扣以及多项特权',
|
||||||
pay_user_vip_more: '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>',
|
pay_user_vip_more: '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>',
|
||||||
pay_download_limit: 0,
|
pay_download_limit: 0,
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* 提现策略 — 提现门槛、每日限制、收款渠道与提现协议。
|
* 提现策略 — 提现门槛、每日限制、收款渠道与提现协议。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** 默认收款渠道:支付宝与微信,与参考主题一致。 */
|
||||||
|
export function defaultPayoutChannels() {
|
||||||
|
return [
|
||||||
|
{ key: 'alipay', label: '支付宝收款码', account_hint: '姓名 + 支付宝账号', note: '', enabled: true },
|
||||||
|
{ key: 'wechat', label: '微信收款码', account_hint: '姓名 + 微信号', note: '', enabled: true },
|
||||||
|
]
|
||||||
|
}
|
||||||
export const withdrawalSchema = {
|
export const withdrawalSchema = {
|
||||||
key: 'payment.withdrawal',
|
key: 'payment.withdrawal',
|
||||||
title: '提现策略',
|
title: '提现策略',
|
||||||
@@ -12,7 +20,7 @@ export const withdrawalSchema = {
|
|||||||
affiliate_daily_withdrawal_count: 3,
|
affiliate_daily_withdrawal_count: 3,
|
||||||
affiliate_daily_withdrawal_limit: '500.00',
|
affiliate_daily_withdrawal_limit: '500.00',
|
||||||
affiliate_withdrawal_reset_time: '00:00',
|
affiliate_withdrawal_reset_time: '00:00',
|
||||||
affiliate_payout_channels: ['alipay', 'wechat'],
|
affiliate_payout_channels: defaultPayoutChannels(),
|
||||||
pay_rebate_withdraw_text_details: '<div>可提现金额达到50元后即可发起提现</div><div>申请提现需后台人工处理,一般2-3小时,请耐心等待</div><div>如有其它疑问,请与客服联系</div>',
|
pay_rebate_withdraw_text_details: '<div>可提现金额达到50元后即可发起提现</div><div>申请提现需后台人工处理,一般2-3小时,请耐心等待</div><div>如有其它疑问,请与客服联系</div>',
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
@@ -40,7 +48,21 @@ export const withdrawalSchema = {
|
|||||||
title: '收款渠道',
|
title: '收款渠道',
|
||||||
icon: 'fa fas fa-qrcode',
|
icon: 'fa fas fa-qrcode',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'affiliate_payout_channels', label: '可用收款渠道', type: 'checkbox', default: ['alipay', 'wechat'], sanitize_type: 'array', options: [{ label: '支付宝收款码', value: 'alipay' }, { label: '微信收款码', value: 'wechat' }] },
|
{
|
||||||
|
key: 'affiliate_payout_channels',
|
||||||
|
label: '收款渠道',
|
||||||
|
type: 'repeater',
|
||||||
|
addText: '添加收款渠道',
|
||||||
|
default: defaultPayoutChannels(),
|
||||||
|
description: '用户提现时可选择的收款方式,可拖拽排序、自定义名称。停用的渠道不会在前台展示,但历史提现记录仍按原名称显示。',
|
||||||
|
fields: [
|
||||||
|
{ key: 'key', label: '渠道标识', type: 'input', default: '', help: '英文小写,用于区分渠道(如 alipay、wechat、bank)。保存后请勿随意修改,历史提现记录依赖它。' },
|
||||||
|
{ key: 'label', label: '渠道名称', type: 'input', default: '', help: '前台下拉里显示的名称,可自定义。' },
|
||||||
|
{ key: 'account_hint', label: '收款信息提示', type: 'input', default: '', help: '用户填写收款账户时的占位提示,如「姓名 + 支付宝账号」。' },
|
||||||
|
{ key: 'note', label: '渠道说明', type: 'textarea', default: '', rows: 2, help: '选中该渠道时在收款信息下方展示的补充说明,可留空。' },
|
||||||
|
{ key: 'enabled', label: '启用', type: 'switch', default: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* User type options shared by the upload limit repeaters.
|
* User type options shared by the upload limit repeaters.
|
||||||
* Mirrors AKSRT\Attachment\UploadLimits::user_type_options() (levels 2-10, VIP 1-2).
|
* Mirrors Jio\Attachment\UploadLimits::user_type_options() (levels 2-10, VIP 1-2).
|
||||||
*/
|
*/
|
||||||
const uploadUserTypeOptions = [
|
const uploadUserTypeOptions = [
|
||||||
{ label: '最小默认值(所有人)', value: 'default' },
|
{ label: '最小默认值(所有人)', value: 'default' },
|
||||||
@@ -84,9 +84,11 @@ export const permissionsSchemas = {
|
|||||||
{ label: '资源下载', value: 'download' },
|
{ label: '资源下载', value: 'download' },
|
||||||
{ label: '每日签到', value: 'signin' },
|
{ label: '每日签到', value: 'signin' },
|
||||||
{ label: '前台投稿', value: 'front_submit' },
|
{ label: '前台投稿', value: 'front_submit' },
|
||||||
|
{ label: '论坛发帖', value: 'forum_post' },
|
||||||
{ label: '打赏用户', value: 'tip_user' },
|
{ label: '打赏用户', value: 'tip_user' },
|
||||||
{ label: '提交工单', value: 'ticket_create' },
|
{ label: '提交工单', value: 'ticket_create' },
|
||||||
{ label: '回复工单', value: 'ticket_reply' },
|
{ label: '回复工单', value: 'ticket_reply' },
|
||||||
|
{ label: '友情链接申请', value: 'friend_link' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -130,10 +132,6 @@ export const permissionsSchemas = {
|
|||||||
description: '管理评论审核、反垃圾、限流与拦截配置。',
|
description: '管理评论审核、反垃圾、限流与拦截配置。',
|
||||||
defaults: {
|
defaults: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
akismet_enabled: false,
|
|
||||||
akismet_site_url: '',
|
|
||||||
akismet_blog_lang: 'zh-CN',
|
|
||||||
akismet_api_key: '',
|
|
||||||
ai_enabled: false,
|
ai_enabled: false,
|
||||||
ai_moderate_comments: true,
|
ai_moderate_comments: true,
|
||||||
ai_moderate_background: true,
|
ai_moderate_background: true,
|
||||||
@@ -157,17 +155,6 @@ export const permissionsSchemas = {
|
|||||||
{ key: 'enabled', label: '启用审核系统', type: 'switch', help: '关闭后评论将不经过主题审核链路。' },
|
{ key: 'enabled', label: '启用审核系统', type: 'switch', help: '关闭后评论将不经过主题审核链路。' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'akismet',
|
|
||||||
title: 'Akismet',
|
|
||||||
icon: 'fa fas fa-filter',
|
|
||||||
fields: [
|
|
||||||
{ key: 'akismet_enabled', label: '启用 Akismet', type: 'switch', dependsOn: { key: 'enabled', value: true } },
|
|
||||||
{ key: 'akismet_site_url', label: 'Site URL', type: 'input', placeholder: 'https://your-site.com', dependsOn: { key: 'akismet_enabled', value: true } },
|
|
||||||
{ key: 'akismet_blog_lang', label: '博客语言', type: 'input', placeholder: 'zh-CN', dependsOn: { key: 'akismet_enabled', value: true } },
|
|
||||||
{ key: 'akismet_api_key', label: 'API Key', type: 'input', inputType: 'password', placeholder: 'Akismet API Key', dependsOn: { key: 'akismet_enabled', value: true } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'ai',
|
key: 'ai',
|
||||||
title: 'AI 审核',
|
title: 'AI 审核',
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const commentSchema = {
|
|||||||
type: 'content',
|
type: 'content',
|
||||||
ui_only: true,
|
ui_only: true,
|
||||||
dependsOn: { key: 'close_comments', value: false },
|
dependsOn: { key: 'close_comments', value: false },
|
||||||
content: '<p class="aksrt-hint"><i class="fa fas fa-fw fa-info-circle"></i> WordPress默认关闭评论翻页功能,如需启用请按此进行设置:<br/>1. 进入WP讨论设置,勾选 <code>分页显示评论</code><br/>2. 设置默认显示 <code>最前</code>一页<br/>3. 设置在每个页面顶部显示 <code>旧的</code>评论<br/>4. 根据需要设置每一页显示数量<br/>5. 根据需要设置评论嵌套(推荐开启并设置为3层)<br/>6. wp默认开启免登陆评论,如需关闭则勾选<code>需要登录后才能发表评论</code><br/><a href="options-discussion.php">【去设置】</a></p>',
|
content: '<p class="jio-hint"><i class="fa fas fa-fw fa-info-circle"></i> WordPress默认关闭评论翻页功能,如需启用请按此进行设置:<br/>1. 进入WP讨论设置,勾选 <code>分页显示评论</code><br/>2. 设置默认显示 <code>最前</code>一页<br/>3. 设置在每个页面顶部显示 <code>旧的</code>评论<br/>4. 根据需要设置每一页显示数量<br/>5. 根据需要设置评论嵌套(推荐开启并设置为3层)<br/>6. wp默认开启免登陆评论,如需关闭则勾选<code>需要登录后才能发表评论</code><br/><a href="options-discussion.php">【去设置】</a></p>',
|
||||||
},
|
},
|
||||||
{ key: 'close_comments', label: '关闭文章评论功能', type: 'switch', default: false, description: '部分网站无需交互,或需备案审核,可在此关闭所有文章的评论功能。同时每一篇文章可单独关闭评论功能' },
|
{ key: 'close_comments', label: '关闭文章评论功能', type: 'switch', default: false, description: '部分网站无需交互,或需备案审核,可在此关闭所有文章的评论功能。同时每一篇文章可单独关闭评论功能' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,24 +22,8 @@ export const featureSchema = {
|
|||||||
title: '',
|
title: '',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'article_nav', label: '文章目录树', type: 'switch', default: true, description: '默认开关,每篇文章可单独设置。开启后请自行添加文章目录树模块到侧边栏(文章内标题超过3个才会显示)' },
|
{ key: 'article_nav', label: '文章目录树', type: 'switch', default: true, description: '默认开关,每篇文章可单独设置。开启后请自行添加文章目录树模块到侧边栏(文章内标题超过3个才会显示)' },
|
||||||
{
|
|
||||||
key: 'article_nav_tutorial',
|
|
||||||
label: '',
|
|
||||||
type: 'content',
|
|
||||||
ui_only: true,
|
|
||||||
dependsOn: { key: 'article_nav', value: true },
|
|
||||||
content: '<p class="aksrt-hint"><a href="https://www.zibll.com/1717.html" target="_blank">【查看官网教程】</a></p>',
|
|
||||||
},
|
|
||||||
{ key: 'article_nav_mobile_nav_s', label: '在移动端弹出菜单内显示', type: 'switch', default: true, dependsOn: { key: 'article_nav', value: true }, description: '如需显示在侧边栏等其它位置,请使用小工具模块添加' },
|
{ key: 'article_nav_mobile_nav_s', label: '在移动端弹出菜单内显示', type: 'switch', default: true, dependsOn: { key: 'article_nav', value: true }, description: '如需显示在侧边栏等其它位置,请使用小工具模块添加' },
|
||||||
{ key: 'imagelightbox', label: '图片灯箱', type: 'switch', default: true, description: '点击图片查看原图功能,共两种模式' },
|
{ key: 'imagelightbox', label: '图片灯箱', type: 'switch', default: true, description: '点击图片查看原图功能,共两种模式' },
|
||||||
{
|
|
||||||
key: 'imagelightbox_tutorial',
|
|
||||||
label: '',
|
|
||||||
type: 'content',
|
|
||||||
ui_only: true,
|
|
||||||
dependsOn: { key: 'imagelightbox', value: true },
|
|
||||||
content: '<p class="aksrt-hint"><a href="https://www.zibll.com/683.html" target="_blank">【查看官网教程】</a></p>',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'imagelightbox_type',
|
key: 'imagelightbox_type',
|
||||||
label: '图片灯箱模式',
|
label: '图片灯箱模式',
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const filterSchema = {
|
|||||||
key: 'filter_intro',
|
key: 'filter_intro',
|
||||||
type: 'content',
|
type: 'content',
|
||||||
ui_only: true,
|
ui_only: true,
|
||||||
content: '<p class="aksrt-hint"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span class="aksrt-warn">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、标签等系统已存在的类型</span><br/><a href="https://www.zibll.com/15886.html" target="_blank">查看官网教程</a></p>',
|
content: '<p class="jio-hint"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span class="jio-warn">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category,topics等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、专题、标签等系统已存在的类型</span></p>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'custom_filter_show',
|
key: 'custom_filter_show',
|
||||||
@@ -28,6 +28,7 @@ export const filterSchema = {
|
|||||||
options: [
|
options: [
|
||||||
{ label: '分类页面显示', value: 'cat' },
|
{ label: '分类页面显示', value: 'cat' },
|
||||||
{ label: '标签页面显示', value: 'tag' },
|
{ label: '标签页面显示', value: 'tag' },
|
||||||
|
{ label: '专题页面显示', value: 'topics' },
|
||||||
],
|
],
|
||||||
description: '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选',
|
description: '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选',
|
||||||
},
|
},
|
||||||
@@ -87,6 +88,18 @@ export const filterSchema = {
|
|||||||
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false, description: '用下拉框显示更多内容' },
|
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false, description: '用下拉框显示更多内容' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ key: 'topics_s', label: '专题筛选', type: 'switch', default: false, description: '在所选分类页显示的专题筛选列表' },
|
||||||
|
{
|
||||||
|
key: 'topics_lists',
|
||||||
|
label: ' ',
|
||||||
|
type: 'fieldset',
|
||||||
|
default: { lists: [], dropdown: false },
|
||||||
|
dependsOn: { key: 'topics_s', value: true },
|
||||||
|
fields: [
|
||||||
|
{ key: 'lists', label: '按钮列表', type: 'async_select', source: 'topics', multiple: true, sortable: true, default: [] },
|
||||||
|
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
{ key: 'orderby_s', label: '排序方式', type: 'switch', default: true, description: '在所选分类页显示排序方式按钮' },
|
{ key: 'orderby_s', label: '排序方式', type: 'switch', default: true, description: '在所选分类页显示排序方式按钮' },
|
||||||
{
|
{
|
||||||
key: 'orderby_lists',
|
key: 'orderby_lists',
|
||||||
|
|||||||
Loaded 100 of 1172 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user