fix: 修正打包脚本插件目录名(addon)、重建框架文件清单、修复 install.sql(sms 表冲突/菜单归属/权限名拼写)
This commit is contained in:
@@ -13,7 +13,7 @@ namespace addon\appmall;
|
||||
|
||||
use think\facade\Config;
|
||||
use think\facade\Db;
|
||||
use ywxapp\AddonBase;
|
||||
use ywxapp\Addons;
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ use ywxapp\model\BaseModel;
|
||||
* 安装时由框架自动导入 install.sql 建表(appmall_addon_list / appmall_addon_submissions / appmall_developers);
|
||||
* 卸载时清理这些服务端表。
|
||||
*/
|
||||
class Addon extends addon
|
||||
class Addon extends Addons
|
||||
{
|
||||
|
||||
public function install(): bool
|
||||
|
||||
@@ -15,7 +15,7 @@ use addon\appmall\library\AppmallSchema;
|
||||
* 开发者自助注册 / 查询令牌 / 收益查询 / 提现;完全公开(noNeedLogin=*),
|
||||
* 以开发者令牌(token)而非会员登录态认证。
|
||||
* 路由前缀:/appmall/developer/*(由 AppService::loadAddonRoutes 在 boot 阶段包 Route::group('appmall') 注册到全局路由表,不隶属于任何 thinkphp 应用)。
|
||||
* 视图:addon/appmall/view/frontend/developer/。
|
||||
* 视图:addons/appmall/view/frontend/developer/。
|
||||
*
|
||||
* 注:AddonFrontend 基类 _initialize 把 view_path 设成【主应用】view/frontend/(与插件目录不符),
|
||||
* 故此处必须在 initialize() 显式把 view_path 锁回插件目录,否则 fetch 找不到模板。
|
||||
|
||||
@@ -20,7 +20,7 @@ use ywxapp\controller\FrontendBase;
|
||||
* 列出已发布的主框架版本,提供「整包 / 增量补丁 / 完整安装包」下载入口。
|
||||
* 下载走对外 API /appmall/api/framework/download(无需登录,可选签名)。
|
||||
* 路由前缀:/appmall/framework/*(由 AppService::loadAddonRoutes 外层包 Route::group('appmall'))。
|
||||
* 视图:addon/appmall/view/frontend/framework/。
|
||||
* 视图:addons/appmall/view/frontend/framework/。
|
||||
*/
|
||||
class Framework extends FrontendBase
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ use addon\appmall\service\MarketService;
|
||||
* 列出应用市场上线的插件 / 模板,卡片展示 logo、价格、评分、下载量等。
|
||||
* 数据来自中心站本地库(appmall_addon_list),复用 MarketService::catalog()。
|
||||
* 路由前缀:/appmall/store/*(由 AppService::loadAddonRoutes 外层包 Route::group('appmall'))。
|
||||
* 视图:addon/appmall/view/frontend/store/。
|
||||
* 视图:addons/appmall/view/frontend/store/。
|
||||
*/
|
||||
class Store extends FrontendBase
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ use ywxapp\library\StreamZipResponse;
|
||||
*
|
||||
* 由原核心 app/api/controller/Market.php 剥离而来,部署在「插件服务器(中心站)」的 market 插件内。
|
||||
* 接口契约对齐 ywxapp 客户端 ywxapp\service\AddonService:
|
||||
* - GET /api/addon/lists 插件列表(addon::market 使用)
|
||||
* - GET /api/addon/lists 插件列表(Addons::market 使用)
|
||||
* - POST /api/addon/valid 安装前授权校验(AddonService::valid 使用,multipart)
|
||||
* - GET /api/index 下载插件 zip(AddonService::download 使用,/api/index/index 规范化)
|
||||
* - POST /api/addon/submit 开发者提交插件(待审核)
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
return [
|
||||
'name' => 'appmall',
|
||||
'title' => '插件应用市场(服务端)',
|
||||
'title' => '应用市场',
|
||||
'intro' => '应用服务中心(插件商店 + 主框架在线升级)服务端:插件市场列表 / 下载 / 授权校验 / 开发者提交与审核,以及主框架核心包版本发布与升级。部署在「中心站」,客户端通过 config ywxapp.api_url 连接。',
|
||||
'author' => 'YwxApp',
|
||||
'website' => 'https://www.ywxapp.cn',
|
||||
'version' => '1.0.19',
|
||||
'version' => '1.0.20',
|
||||
'state' => 1,
|
||||
'license' => false,
|
||||
'events' => [
|
||||
@@ -15,5 +15,6 @@ return [
|
||||
],
|
||||
'services' => [
|
||||
],
|
||||
'install_time' => 1785173040,
|
||||
'install_time' => 1786292662,
|
||||
'update_time' => 1786808656,
|
||||
];
|
||||
+216
-197
@@ -1,215 +1,234 @@
|
||||
-- ============================================================
|
||||
-- 应用市场(appmall)插件安装表结构
|
||||
-- 事实源:addon/appmall/library/AppmallSchema.php::ensure()
|
||||
-- 与运行时自愈 DDL 一致;扩展列已合并进建表,全新安装即完整。
|
||||
-- 插件应用市场(服务端)建表脚本
|
||||
-- 由框架插件安装流程(AddonService::executeInstall)自动导入。
|
||||
-- 部署「插件服务器(中心站)」并启用 market 插件后即生效。
|
||||
-- 接口契约见 addons/appmall/controller/api/Market.php
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_orders` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL DEFAULT 0,
|
||||
`aid` int unsigned NOT NULL DEFAULT 0 COMMENT 'appmall_addon_list.id',
|
||||
`site_id` int unsigned NOT NULL DEFAULT 0 COMMENT '下单站点',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT 0,
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0=待支付 1=已支付 2=已退款',
|
||||
`trade_no` varchar(64) NOT NULL DEFAULT '' COMMENT '商户订单号',
|
||||
`pay_time` int unsigned DEFAULT 0,
|
||||
`create_at` int unsigned DEFAULT 0,
|
||||
`update_at` int unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_uid` (`uid`),
|
||||
KEY `idx_trade` (`trade_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件购买订单表';
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_list` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL COMMENT '插件标识符,如 mqttbroker',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'addon' COMMENT '商品类型:addon=插件 template=模板',
|
||||
`developer_id` int unsigned DEFAULT '0' COMMENT '发布者(wxapp_appmall_developers.id),用于收益归属',
|
||||
`title` varchar(100) NOT NULL COMMENT '插件名称',
|
||||
`version` varchar(20) NOT NULL COMMENT '版本号',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '价格,0 为免费',
|
||||
`author` varchar(100) DEFAULT '' COMMENT '作者',
|
||||
`description` text COMMENT '简介/描述',
|
||||
`changelog` text COMMENT '更新日志',
|
||||
`require_framework` varchar(20) DEFAULT '' COMMENT '最低兼容框架版本,如 1.2.0',
|
||||
`logo` varchar(255) DEFAULT '' COMMENT '封面图 URL',
|
||||
`category` varchar(50) DEFAULT '' COMMENT '分类(如:工具/电商/社交)',
|
||||
`tags` varchar(255) DEFAULT '' COMMENT '标签(逗号分隔)',
|
||||
`screenshots` text COMMENT '截图 URL(逗号分隔或 JSON 数组)',
|
||||
`rating` decimal(2,1) DEFAULT '0.0' COMMENT '评分(0.0~5.0)',
|
||||
`file_path` varchar(255) NOT NULL COMMENT 'zip 物理路径(非 Web 可访问目录)',
|
||||
`file_hash` varchar(64) NOT NULL COMMENT 'SHA256 校验值',
|
||||
`status` tinyint DEFAULT 1 COMMENT '1=上架 0=下架',
|
||||
`download_count` int DEFAULT 0 COMMENT '下载次数',
|
||||
`create_at` int DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int DEFAULT 0 COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_name_version` (`name`, `version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件市场商品表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_licenses` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL,
|
||||
`aid` int unsigned NOT NULL,
|
||||
`license_key` varchar(64) NOT NULL COMMENT '授权码',
|
||||
`site_id` int unsigned NOT NULL DEFAULT 0 COMMENT '绑定站点',
|
||||
`domain` varchar(255) DEFAULT NULL COMMENT '绑定域名(站点授权)',
|
||||
`status` tinyint NOT NULL DEFAULT 1 COMMENT '1=有效 0=已吊销',
|
||||
`expire_time` int unsigned DEFAULT 0 COMMENT '0为永久',
|
||||
`download_count` int DEFAULT 0 COMMENT '下载次数限制',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `license_key` (`license_key`),
|
||||
UNIQUE KEY `uk_uid_aid` (`uid`, `aid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件授权表';
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_submissions` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned DEFAULT '0' COMMENT '提交者(wxapp_appmall_developers.id)',
|
||||
`name` varchar(50) NOT NULL COMMENT '插件标识符',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'addon' COMMENT '商品类型:addon=插件 template=模板',
|
||||
`title` varchar(100) NOT NULL COMMENT '插件名称',
|
||||
`author` varchar(100) DEFAULT '' COMMENT '作者',
|
||||
`description` text COMMENT '简介/描述',
|
||||
`changelog` text COMMENT '更新日志',
|
||||
`require_framework` varchar(20) DEFAULT '' COMMENT '最低兼容框架版本,如 1.2.0',
|
||||
`version` varchar(20) NOT NULL COMMENT '版本号',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '价格',
|
||||
`category` varchar(50) DEFAULT '' COMMENT '分类',
|
||||
`tags` varchar(255) DEFAULT '' COMMENT '标签(逗号分隔)',
|
||||
`logo` varchar(255) DEFAULT '' COMMENT '封面图',
|
||||
`file_path` varchar(255) NOT NULL COMMENT '待审 zip 物理路径',
|
||||
`file_hash` varchar(64) NOT NULL COMMENT 'SHA256',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待审核 1=已通过 2=已驳回',
|
||||
`reject_reason` varchar(255) DEFAULT '' COMMENT '驳回原因',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_name_version` (`name`, `version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件市场提交审核表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_download_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`license_id` int unsigned NOT NULL DEFAULT 0,
|
||||
`uid` int unsigned NOT NULL DEFAULT 0,
|
||||
`aid` int unsigned NOT NULL DEFAULT 0,
|
||||
`operator_id` int unsigned NOT NULL DEFAULT 0 COMMENT '运营后台安装者(特权放行审计)',
|
||||
`ip` varchar(45) DEFAULT '',
|
||||
`create_at` int unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_license` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件下载日志表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_throttle` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`action` varchar(30) NOT NULL COMMENT '限流动作(buy/notify/download/submit)',
|
||||
`ident` varchar(80) NOT NULL COMMENT '限流标识(uid 或 IP)',
|
||||
`count` int unsigned NOT NULL DEFAULT 0 COMMENT '窗口内已请求次数',
|
||||
`expire_at` int unsigned NOT NULL DEFAULT 0 COMMENT '窗口过期时间戳',
|
||||
`create_at` int unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_action_ident` (`action`, `ident`),
|
||||
KEY `idx_expire` (`expire_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='接口频率限制表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_review_log` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`submission_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'appmall_addon_submissions.id',
|
||||
`operator_id` int unsigned NOT NULL DEFAULT 0 COMMENT '操作管理员ID',
|
||||
`action` varchar(20) NOT NULL COMMENT '操作:submit/approve/reject',
|
||||
`from_status` tinyint NOT NULL DEFAULT 0 COMMENT '变更前状态',
|
||||
`to_status` tinyint NOT NULL DEFAULT 0 COMMENT '变更后状态',
|
||||
`reason` varchar(255) NOT NULL DEFAULT '' COMMENT '审核意见/驳回理由',
|
||||
`create_at` int unsigned NOT NULL DEFAULT 0 COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_submission` (`submission_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件审核历史日志(可视化时间线数据源)';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_developers` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(100) NOT NULL COMMENT '开发者名称',
|
||||
`email` varchar(120) NOT NULL COMMENT '邮箱(接收令牌/通知)',
|
||||
`token` varchar(64) NOT NULL COMMENT '开发者令牌(发布插件时校验)',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待审核 1=正常 -1=禁用',
|
||||
`balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '可提现余额',
|
||||
`frozen_balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '待结算/冻结金额',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_email` (`email`),
|
||||
UNIQUE KEY `uk_token` (`token`)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_developers` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(100) NOT NULL COMMENT '开发者名称',
|
||||
`email` varchar(120) NOT NULL COMMENT '邮箱(接收令牌/通知)',
|
||||
`token` varchar(64) NOT NULL COMMENT '开发者令牌(发布插件时校验)',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待审核 1=正常 -1=禁用',
|
||||
`balance` decimal(10,2) DEFAULT '0.00' COMMENT '可提现余额',
|
||||
`frozen_balance` decimal(10,2) DEFAULT '0.00' COMMENT '待结算/冻结金额',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_email` (`email`),
|
||||
UNIQUE KEY `uk_token` (`token`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='市场开发者表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_revenues` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT '0' COMMENT '受益开发者',
|
||||
`order_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'appmall_addon_orders.id',
|
||||
`aid` int unsigned NOT NULL DEFAULT '0' COMMENT 'appmall_addon_list.id',
|
||||
`uid` int unsigned NOT NULL DEFAULT '0' COMMENT '购买用户',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
|
||||
`commission` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '平台抽成',
|
||||
`income` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '开发者应得',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待结算 1=已结算',
|
||||
`create_at` int DEFAULT 0,
|
||||
`settle_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_developer` (`developer_id`),
|
||||
KEY `idx_order` (`order_id`)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_revenues` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT '0' COMMENT '受益开发者',
|
||||
`order_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'wxapp_appmall_addon_orders.id',
|
||||
`aid` int unsigned NOT NULL DEFAULT '0' COMMENT 'wxapp_appmall_addon_list.id',
|
||||
`uid` int unsigned NOT NULL DEFAULT '0' COMMENT '购买用户',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
|
||||
`commission` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '平台抽成',
|
||||
`income` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '开发者应得',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待结算 1=已结算',
|
||||
`create_at` int DEFAULT 0,
|
||||
`settle_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_developer` (`developer_id`),
|
||||
KEY `idx_order` (`order_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件销售收益账本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_withdrawals` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT '0',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '提现金额',
|
||||
`channel` varchar(20) DEFAULT 'alipay' COMMENT '提现渠道',
|
||||
`account` varchar(100) DEFAULT '' COMMENT '收款账号',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待处理 1=已打款 -1=驳回',
|
||||
`remark` varchar(255) DEFAULT '',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_developer` (`developer_id`)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_withdrawals` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT '0',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '提现金额',
|
||||
`channel` varchar(20) DEFAULT 'alipay' COMMENT '提现渠道',
|
||||
`account` varchar(100) DEFAULT '' COMMENT '收款账号',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待处理 1=已打款 -1=驳回',
|
||||
`remark` varchar(255) DEFAULT '',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_developer` (`developer_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开发者提现单';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_submissions` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'appmall_developers.id',
|
||||
`name` varchar(50) NOT NULL COMMENT '插件标识符',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'addon' COMMENT '商品类型:addon=插件 template=模板',
|
||||
`title` varchar(100) NOT NULL DEFAULT '' COMMENT '名称',
|
||||
`author` varchar(100) DEFAULT '' COMMENT '作者',
|
||||
`description` text COMMENT '简介',
|
||||
`version` varchar(20) NOT NULL COMMENT '版本号',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '价格',
|
||||
`logo` varchar(255) DEFAULT '' COMMENT '封面',
|
||||
`category` varchar(50) DEFAULT '' COMMENT '分类',
|
||||
`tags` varchar(255) DEFAULT '' COMMENT '标签',
|
||||
`file_path` varchar(255) NOT NULL COMMENT '待审包路径',
|
||||
`file_hash` varchar(64) NOT NULL COMMENT 'SHA256',
|
||||
`changelog` text COMMENT '更新日志',
|
||||
`require_framework` varchar(20) DEFAULT '' COMMENT '最低兼容框架版本,如 1.2.0',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0待审 1通过 2驳回',
|
||||
`reject_reason` varchar(255) DEFAULT '' COMMENT '驳回原因',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_name_ver` (`name`, `version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件提交审核表';
|
||||
-- ============================================================
|
||||
-- 以下三张表是「购买/授权闭环」的运行表(订单 / 授权 / 下载日志)。
|
||||
-- 旧版 install.sql 漏建,导致新装中心站一调 buy/notify/下载就报「表不存在」。
|
||||
-- 与 docs/appmall_schema.sql、客户端 app/api/controller/v1/Addon.php::ensureTables() 字段保持一致。
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_addon_list` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL COMMENT '标识符',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'addon' COMMENT '商品类型:addon=插件 template=模板',
|
||||
`developer_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'appmall_developers.id',
|
||||
`title` varchar(100) NOT NULL COMMENT '名称',
|
||||
`version` varchar(20) NOT NULL COMMENT '版本号',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '价格',
|
||||
`author` varchar(100) DEFAULT '' COMMENT '作者',
|
||||
`description` text COMMENT '简介/描述',
|
||||
`logo` varchar(255) DEFAULT '' COMMENT '封面图 URL',
|
||||
`file_path` varchar(255) NOT NULL COMMENT '存储路径(非公开)',
|
||||
`file_hash` varchar(64) NOT NULL COMMENT 'SHA256校验值',
|
||||
`category` varchar(50) DEFAULT '' COMMENT '分类',
|
||||
`tags` varchar(255) DEFAULT '' COMMENT '标签(逗号分隔)',
|
||||
`screenshots` text COMMENT '截图URL(逗号分隔或JSON)',
|
||||
`rating` decimal(2,1) DEFAULT '0.0' COMMENT '评分(0.0~5.0)',
|
||||
`status` tinyint DEFAULT 1 COMMENT '1上架 0下架',
|
||||
`download_count` int DEFAULT 0 COMMENT '下载次数',
|
||||
`changelog` text COMMENT '更新日志',
|
||||
`require_framework` varchar(20) DEFAULT '' COMMENT '最低兼容框架版本,如 1.2.0',
|
||||
`create_at` int DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int DEFAULT 0 COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='市场插件列表';
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_licenses` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL COMMENT '用户ID',
|
||||
`aid` int unsigned NOT NULL COMMENT '对应 appmall_addon_list.id',
|
||||
`license_key` varchar(64) NOT NULL COMMENT '授权码',
|
||||
`site_id` int unsigned NOT NULL DEFAULT '0' COMMENT '绑定站点(wxapp_appmall_sites.id),0=未绑定',
|
||||
`domain` varchar(255) DEFAULT NULL COMMENT '绑定域名(站点授权,对齐 Discuz!)',
|
||||
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1=有效 0=已吊销(退款)',
|
||||
`expire_time` int unsigned DEFAULT '0' COMMENT '0 为永久',
|
||||
`download_count` int DEFAULT '0' COMMENT '下载次数限制',
|
||||
`create_at` int DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int DEFAULT 0 COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `license_key` (`license_key`),
|
||||
UNIQUE KEY `uk_uid_aid` (`uid`, `aid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件授权表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_sites` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`site_token` varchar(64) NOT NULL DEFAULT '' COMMENT '站点令牌(客户端 install 时生成/上报)',
|
||||
`domain` varchar(255) NOT NULL DEFAULT '' COMMENT '主域名',
|
||||
`owner_uid` int unsigned NOT NULL DEFAULT '0' COMMENT '站点所有者用户ID',
|
||||
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1=正常 0=禁用',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_domain` (`domain`),
|
||||
KEY `idx_token` (`site_token`)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_orders` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL DEFAULT '0',
|
||||
`aid` int unsigned NOT NULL DEFAULT '0' COMMENT 'appmall_addon_list.id',
|
||||
`site_id` int unsigned NOT NULL DEFAULT '0' COMMENT '下单站点',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0' COMMENT '订单金额',
|
||||
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0=待支付 1=已支付 2=已退款',
|
||||
`trade_no` varchar(64) NOT NULL DEFAULT '' COMMENT '商户订单号',
|
||||
`pay_time` int unsigned DEFAULT '0' COMMENT '支付时间',
|
||||
`create_at` int unsigned DEFAULT 0,
|
||||
`update_at` int unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_uid` (`uid`),
|
||||
KEY `idx_trade` (`trade_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件购买订单表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_download_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`license_id` int unsigned NOT NULL DEFAULT '0',
|
||||
`uid` int unsigned NOT NULL DEFAULT '0',
|
||||
`aid` int unsigned NOT NULL DEFAULT '0',
|
||||
`operator_id` int unsigned NOT NULL DEFAULT '0' COMMENT '运营后台下载审计(Addons::downloadInstall 透传,0=会员自助下载)',
|
||||
`ip` varchar(45) DEFAULT '',
|
||||
`create_at` int unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_license` (`license_id`),
|
||||
KEY `idx_operator` (`operator_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件下载日志表';
|
||||
|
||||
-- 市场客户端站点(授权绑定的维度,对齐 Discuz!「授权绑定站点域名」)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_sites` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`site_token` varchar(64) NOT NULL DEFAULT '' COMMENT '站点令牌(客户端 install 时生成/上报)',
|
||||
`domain` varchar(255) NOT NULL DEFAULT '' COMMENT '主域名',
|
||||
`owner_uid` int unsigned NOT NULL DEFAULT '0' COMMENT '站点所有者用户ID',
|
||||
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1=正常 0=禁用',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_domain` (`domain`),
|
||||
KEY `idx_token` (`site_token`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='市场客户端站点(授权绑定维度)';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_framework_list` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`version` varchar(20) NOT NULL COMMENT '框架版本号',
|
||||
`title` varchar(100) DEFAULT '' COMMENT '版本标题',
|
||||
`changelog` text COMMENT '更新日志',
|
||||
`file_path` varchar(255) DEFAULT '' COMMENT '整包 zip 物理路径(非 Web 可访问目录)',
|
||||
`file_hash` varchar(64) DEFAULT '' COMMENT '整包 SHA256 校验值',
|
||||
`patch_path` varchar(255) DEFAULT '' COMMENT '增量补丁 zip 物理路径(与整包同版本共存)',
|
||||
`patch_hash` varchar(64) DEFAULT '' COMMENT '补丁 SHA256 校验值',
|
||||
`patch_from` varchar(20) DEFAULT '' COMMENT '补丁适用的基础版本',
|
||||
`type` tinyint DEFAULT 0 COMMENT '兼容旧客户端:0=含整包 1=仅补丁',
|
||||
`from_version` varchar(20) DEFAULT '' COMMENT '兼容旧客户端:补丁基础版本镜像',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=草稿 1=已发布(可下载)',
|
||||
`download_count` int DEFAULT 0 COMMENT '下载次数',
|
||||
`create_at` int DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int DEFAULT 0 COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_version` (`version`)
|
||||
-- ============================================================
|
||||
-- 主框架升级(原 upgrade 插件,已合并进 market)
|
||||
-- 客户端 ywxapp\service\FrameworkService 经 /api/framework/* 拉取版本与核心包。
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_framework_list` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`version` varchar(20) NOT NULL COMMENT '框架版本号',
|
||||
`title` varchar(100) DEFAULT '' COMMENT '版本标题',
|
||||
`changelog` text COMMENT '更新日志',
|
||||
`file_path` varchar(255) DEFAULT '' COMMENT '整包 zip 物理路径(非 Web 可访问目录)',
|
||||
`file_hash` varchar(64) DEFAULT '' COMMENT '整包 SHA256 校验值',
|
||||
`patch_path` varchar(255) DEFAULT '' COMMENT '增量补丁 zip 物理路径(与整包同版本共存)',
|
||||
`patch_hash` varchar(64) DEFAULT '' COMMENT '补丁 SHA256 校验值',
|
||||
`patch_from` varchar(20) DEFAULT '' COMMENT '补丁适用的基础版本',
|
||||
`type` tinyint DEFAULT 0 COMMENT '兼容旧客户端:0=含整包 1=仅补丁(逻辑以 patch_path 是否为空为准)',
|
||||
`from_version` varchar(20) DEFAULT '' COMMENT '兼容旧客户端:补丁基础版本镜像',
|
||||
`install_path` varchar(255) DEFAULT '' COMMENT '完整安装包 zip 物理路径(全新部署用,含占位符 .env,非 Web 可访问目录)',
|
||||
`install_hash` varchar(64) DEFAULT '' COMMENT '完整安装包 SHA256 校验值',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=草稿 1=已发布(可下载)',
|
||||
`download_count` int DEFAULT 0 COMMENT '下载次数',
|
||||
`create_at` int DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int DEFAULT 0 COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_version` (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主框架版本表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `appmall_framework_download_log` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`version` varchar(20) NOT NULL COMMENT '下载的版本号',
|
||||
`uid` int DEFAULT 0 COMMENT '下载用户ID(来自客户端)',
|
||||
`ip` varchar(45) DEFAULT '' COMMENT '下载来源IP',
|
||||
`create_at` int DEFAULT 0 COMMENT '下载时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_version` (`version`)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_framework_download_log` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`version` varchar(20) NOT NULL COMMENT '下载的版本号',
|
||||
`uid` int DEFAULT 0 COMMENT '下载用户ID(来自客户端)',
|
||||
`ip` varchar(45) DEFAULT '' COMMENT '下载来源IP',
|
||||
`create_at` int DEFAULT 0 COMMENT '下载时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_version` (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主框架下载日志表';
|
||||
|
||||
-- 接口频率限制(buy/notify/download/submit 等防刷)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_throttle` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`action` varchar(30) NOT NULL COMMENT '限流动作(buy/notify/download/submit)',
|
||||
`ident` varchar(80) NOT NULL COMMENT '限流标识(uid 或 IP)',
|
||||
`count` int unsigned NOT NULL DEFAULT 0 COMMENT '窗口内已请求次数',
|
||||
`expire_at` int unsigned NOT NULL DEFAULT 0 COMMENT '窗口过期时间戳',
|
||||
`create_at` int unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_action_ident` (`action`, `ident`),
|
||||
KEY `idx_expire` (`expire_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='接口频率限制表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__appmall_addon_review_log` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`submission_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'appmall_addon_submissions.id',
|
||||
`operator_id` int unsigned NOT NULL DEFAULT 0 COMMENT '操作管理员ID',
|
||||
`action` varchar(20) NOT NULL COMMENT '操作:submit/approve/reject',
|
||||
`from_status` tinyint NOT NULL DEFAULT 0 COMMENT '变更前状态',
|
||||
`to_status` tinyint NOT NULL DEFAULT 0 COMMENT '变更后状态',
|
||||
`reason` varchar(255) NOT NULL DEFAULT '' COMMENT '审核意见/驳回理由',
|
||||
`create_at` int unsigned NOT NULL DEFAULT 0 COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_submission` (`submission_id`),
|
||||
KEY `idx_create` (`create_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件审核历史日志(可视化时间线数据源)';
|
||||
|
||||
+15
-16
@@ -3,7 +3,7 @@
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-07-21 13:36:46
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-16 11:31:01
|
||||
* @LastEditTime: 2026-08-04 22:21:46
|
||||
* @Description: 应用市场插件统一路由(对外 API + 主应用业务路由)
|
||||
* @FilePath: \ywxapp_dev\addon\appmall\route\app.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
@@ -70,26 +70,25 @@ Route::group('backend', function () {
|
||||
Route::post('framework/delete', [Framework::class, 'delete']);
|
||||
Route::get('framework/download', [Framework::class, 'download']);
|
||||
});
|
||||
|
||||
|
||||
// ---------- 开发者中心(独立前台应用,公开注册/令牌/收益/提现,非会员中心):统一前缀 /appmall/developer ----------
|
||||
// 仅【中心站】暴露:开发者自助注册/收益/提现依赖 appmall_developers / appmall_addon_revenues / appmall_addon_withdrawals 等
|
||||
// 运营表,客户机(ywxapp.api_url 指向其他服务器,见 is_market_client())作为中心站客户端不持有这些表,故不在路由层注册,
|
||||
// 访问即 404。配合 Developer 控制器 initialize() 的运行时兜底(即便路由缓存陈旧或被直连也能拦截)。
|
||||
// ---------- 开发者中心(独立前台,令牌认证):/appmall/Developer/* ----------
|
||||
Route::group('Developer', function () {
|
||||
Route::get('index', [DeveloperCenter::class, 'index']);
|
||||
Route::post('save', [DeveloperCenter::class, 'save']);
|
||||
Route::get('mine', [DeveloperCenter::class, 'mine']);
|
||||
Route::post('lookup', [DeveloperCenter::class, 'lookup']);
|
||||
Route::get('dashboard', [DeveloperCenter::class, 'dashboard']);
|
||||
Route::get('console', [DeveloperCenter::class, 'console']);
|
||||
Route::post('overview', [DeveloperCenter::class, 'overview']);
|
||||
Route::post('apps', [DeveloperCenter::class, 'apps']);
|
||||
Route::post('price', [DeveloperCenter::class, 'price']);
|
||||
Route::post('earnings', [DeveloperCenter::class, 'earnings']);
|
||||
Route::post('withdraw', [DeveloperCenter::class, 'withdraw']);
|
||||
});
|
||||
|
||||
|
||||
Route::get('developer/index', [DeveloperCenter::class, 'index']);
|
||||
Route::post('developer/save', [DeveloperCenter::class, 'save']);
|
||||
Route::get('developer/mine', [DeveloperCenter::class, 'mine']);
|
||||
Route::post('developer/lookup', [DeveloperCenter::class, 'lookup']);
|
||||
Route::get('developer/dashboard', [DeveloperCenter::class, 'dashboard']);
|
||||
Route::get('developer/console', [DeveloperCenter::class, 'console']);
|
||||
Route::post('developer/overview', [DeveloperCenter::class, 'overview']);
|
||||
Route::post('developer/apps', [DeveloperCenter::class, 'apps']);
|
||||
Route::post('developer/price', [DeveloperCenter::class, 'price']);
|
||||
Route::post('developer/earnings', [DeveloperCenter::class, 'earnings']);
|
||||
Route::post('developer/withdraw', [DeveloperCenter::class, 'withdraw']);
|
||||
|
||||
// ---------- 对外 API(统一前缀 /appmall/api):远程客户机经 Remote/Addon/FrameworkService 调用 ----------
|
||||
// 由 AppService::loadAddonRoutes() 的 Route::group('appmall') 补全插件名前缀 → /appmall/api/*
|
||||
//(与 /appmall/backend、/appmall/developer 同源同构,均为「插件名开头」约定)。
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
-- ============================================================
|
||||
-- addons/appmall/upgrade.sql
|
||||
-- 用于已安装插件、但缺少新字段/配置的老库手动升级。
|
||||
-- 在数据库客户端执行本文件即可。
|
||||
--
|
||||
-- 重要说明:
|
||||
-- 1) 框架「在线升级」(AddonService::onlineUpgrade) 会自动重导 install.sql
|
||||
-- (CREATE TABLE IF NOT EXISTS 全量幂等),已覆盖 appmall 全部表结构;
|
||||
-- 本文件仅作为 DBA 手动补列 / 历史库修复的参考。
|
||||
-- 2) 框架插件升级流程的 importsql() 白名单仅允许 CREATE/INSERT,不会自动执行
|
||||
-- 本文件的 ALTER;如需在升级时自动收敛缺失列,请走插件 Addon::upgrade() 钩子
|
||||
-- (内部用 SchemaGuard::ensureColumn 先探测后追加,重复执行安全)。
|
||||
-- 3) 纯 ALTER 语句重复执行可能报「列已存在」,属正常,可忽略。
|
||||
-- ============================================================
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 1. 下载日志表:运营后台下载审计字段(install.sql 已含,此处供老库补齐)
|
||||
-- 注意:本文件为 DBA 手动执行脚本,不会被框架自动替换前缀;
|
||||
-- 执行前请将下方 `__PREFIX__` 替换为当前数据库表前缀(如 wxapp_)。
|
||||
ALTER TABLE `__PREFIX__appmall_addon_download_logs`
|
||||
ADD COLUMN `operator_id` int unsigned NOT NULL DEFAULT 0 COMMENT '运营后台下载审计(0=会员自助下载)' AFTER `uid`;
|
||||
|
||||
-- 2. 未来新增列在此追加(同样直接 ALTER,重复执行忽略报错即可)
|
||||
@@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>审核详情 - 应用市场管理</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f2f3f5; padding: 15px; }
|
||||
.info-card { background: #fff; border-radius: 4px; padding: 15px 20px; margin-bottom: 15px; }
|
||||
.info-card h3 { margin: 0 0 12px; font-size: 15px; border-left: 3px solid #1e9fff; padding-left: 8px; }
|
||||
.info-row { display: flex; flex-wrap: wrap; }
|
||||
.info-item { width: 50%; padding: 6px 0; box-sizing: border-box; }
|
||||
.info-item.full { width: 100%; }
|
||||
.info-item .label { color: #999; margin-right: 6px; }
|
||||
.info-item .value { color: #333; }
|
||||
.step-wrap { background: #fff; border-radius: 4px; padding: 20px; margin-bottom: 15px; }
|
||||
.timeline-empty { color: #999; text-align: center; padding: 20px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="info-card">
|
||||
<h3>提交信息</h3>
|
||||
<div class="info-row" id="infoRow">
|
||||
<div class="timeline-empty">加载中…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-wrap">
|
||||
<h3 style="margin:0 0 18px;font-size:15px;border-left:3px solid #1e9fff;padding-left:8px;">审核状态流</h3>
|
||||
<div class="layui-row layui-col-space20" id="stepRow">
|
||||
<div class="timeline-empty">加载中…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<h3>审核历史时间线</h3>
|
||||
<ul class="layui-timeline" id="timeline">
|
||||
<li class="layui-timeline-item"><i class="layui-icon layui-timeline-axis layui-icon-loading"></i><div class="layui-timeline-content layui-text"><div class="timeline-empty">加载中…</div></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
<script>
|
||||
layui.use(['jquery', 'http', 'util'], function () {
|
||||
var $ = layui.jquery, http = layui.http, util = layui.util;
|
||||
var id = {$id};
|
||||
|
||||
function statusText(s) {
|
||||
switch (parseInt(s, 10)) {
|
||||
case 0: return '待审核';
|
||||
case 1: return '已通过';
|
||||
case 2: return '已驳回';
|
||||
default: return '未知(' + s + ')';
|
||||
}
|
||||
}
|
||||
function actionText(a) {
|
||||
switch (a) {
|
||||
case 'submit': return '提交审核';
|
||||
case 'approve': return '审核通过';
|
||||
case 'reject': return '审核驳回';
|
||||
default: return a;
|
||||
}
|
||||
}
|
||||
function fmt(ts) {
|
||||
if (!ts) return '';
|
||||
return util.toDateString(ts * 1000, 'yyyy-MM-dd HH:mm:ss');
|
||||
}
|
||||
|
||||
http.get('detail', { id: id }).then(function (r) {
|
||||
if (r.code !== 0 || !r.data) { $('#infoRow').html('<div class="timeline-empty">加载失败</div>'); return; }
|
||||
var d = r.data;
|
||||
var html = ''
|
||||
+ '<div class="info-item"><span class="label">ID:</span><span class="value">' + (d.id || '') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">标识:</span><span class="value">' + (d.name || '') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">名称:</span><span class="value">' + (d.title || '') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">类型:</span><span class="value">' + (d.type === 'template' ? '模板' : '插件') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">作者:</span><span class="value">' + (d.author || '') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">版本:</span><span class="value">' + (d.version || '') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">价格:</span><span class="value">' + (d.price > 0 ? '¥' + d.price : '免费') + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">当前状态:</span><span class="value">' + statusText(d.status) + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">提交时间:</span><span class="value">' + fmt(d.create_at) + '</span></div>'
|
||||
+ '<div class="info-item"><span class="label">上架状态:</span><span class="value">' + (d.published ? '已上架' : '未上架') + '</span></div>'
|
||||
+ '<div class="info-item full"><span class="label">简介:</span><span class="value">' + (d.description || '-') + '</span></div>'
|
||||
+ '<div class="info-item full"><span class="label">驳回原因:</span><span class="value">' + (d.reject_reason || '-') + '</span></div>';
|
||||
$('#infoRow').html(html);
|
||||
|
||||
// 步骤条:提交 → 审核(通过/驳回)
|
||||
var steps = [
|
||||
{ title: '提交审核', done: true },
|
||||
{ title: '运营审核', done: true, current: d.status != 0, reject: d.status == 2, pass: d.status == 1 }
|
||||
];
|
||||
var stepHtml = '';
|
||||
steps.forEach(function (s) {
|
||||
var cls = 'layui-bg-blue';
|
||||
if (s.reject) cls = 'layui-bg-red';
|
||||
else if (!s.done) cls = 'layui-bg-gray';
|
||||
var icon = '';
|
||||
if (s.reject) icon = 'ဆ';
|
||||
else if (!s.done) icon = '';
|
||||
stepHtml += ''
|
||||
+ '<div class="layui-col-xs6 layui-col-md6" style="text-align:center;">'
|
||||
+ ' <button class="layui-btn layui-btn-circle ' + cls + '">' + icon + '</button>'
|
||||
+ ' <div style="margin-top:6px;">' + s.title + '</div>'
|
||||
+ '</div>';
|
||||
});
|
||||
$('#stepRow').html('<div class="layui-col-xs12" style="display:flex;align-items:center;justify-content:center;">' + stepHtml + '</div>');
|
||||
}).catch(function () { $('#infoRow').html('<div class="timeline-empty">请求失败</div>'); });
|
||||
|
||||
http.get('history', { id: id }).then(function (r) {
|
||||
var arr = (r.code === 0 && r.data) ? r.data : [];
|
||||
if (!arr.length) {
|
||||
$('#timeline').html('<li class="layui-timeline-item"><i class="layui-icon layui-timeline-axis layui-icon-face-surprised"></i><div class="layui-timeline-content layui-text"><div class="timeline-empty">暂无审核记录</div></div></li>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
arr.forEach(function (item) {
|
||||
var color = item.action === 'reject' ? '#ff5722' : (item.action === 'approve' ? '#16b777' : '#1e9fff');
|
||||
html += ''
|
||||
+ '<li class="layui-timeline-item">'
|
||||
+ ' <i class="layui-icon layui-timeline-axis" style="color:' + color + ';"></i>'
|
||||
+ ' <div class="layui-timeline-content layui-text">'
|
||||
+ ' <h4>' + actionText(item.action) + ' <span class="layui-badge layui-bg-gray">' + statusText(item.from_status) + ' → ' + statusText(item.to_status) + '</span></h4>'
|
||||
+ ' <p>操作人ID:' + (item.operator_id || 0) + (item.reason ? ';意见:' + item.reason : '') + '</p>'
|
||||
+ ' <p style="color:#999;">' + fmt(item.create_at) + '</p>'
|
||||
+ ' </div>'
|
||||
+ '</li>';
|
||||
});
|
||||
$('#timeline').html(html);
|
||||
}).catch(function () {
|
||||
$('#timeline').html('<li class="layui-timeline-item"><i class="layui-icon layui-timeline-axis layui-icon-face-surprised"></i><div class="layui-timeline-content layui-text"><div class="timeline-empty">请求失败</div></div></li>');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,93 +1,121 @@
|
||||
|
||||
<div class="layui-card" style="margin:20px;">
|
||||
<div class="layui-card-header">插件审核(开发者上架申请)</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-form layui-row" style="margin-bottom:12px;">
|
||||
<div class="layui-inline">
|
||||
<select id="statusFilter">
|
||||
<option value="">全部状态</option>
|
||||
<option value="0">待审核</option>
|
||||
<option value="1">已发布</option>
|
||||
<option value="2">已驳回</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button class="layui-btn" id="searchBtn">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>插件审核 - 应用市场管理</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>body{background:#f2f3f5;padding:15px;}</style>
|
||||
</head>
|
||||
<body>
|
||||
<form class="layui-form" lay-filter="filterForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline" style="width:140px;">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="0">待审核</option>
|
||||
<option value="1">已通过</option>
|
||||
<option value="2">已驳回</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="layui-btn" lay-submit lay-filter="search">搜索</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/html" id="toolTpl">
|
||||
<table id="dataTable" lay-filter="dataTable"></table>
|
||||
|
||||
<script type="text/html" id="statusTpl">
|
||||
{{# if(d.status == 0){ }}<span class="layui-badge layui-bg-orange">待审核</span>
|
||||
{{# } else if(d.status == 1){ }}<span class="layui-badge layui-bg-green">已通过</span>
|
||||
{{# } else { }}<span class="layui-badge">已驳回</span>{{# } }}
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="operateTpl">
|
||||
<a class="layui-btn layui-btn-xs" href="download?id={{ d.id }}" target="_blank">下载包</a>
|
||||
{{# if(d.status == 0){ }}
|
||||
<a class="layui-btn layui-btn-xs" lay-event="audit">通过</a>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="approve">通过并发布</a>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="reject">驳回</a>
|
||||
{{# } else { }}
|
||||
<span class="layui-badge {{ d.status == 1 ? 'layui-bg-green' : '' }}">
|
||||
{{ d.status == 1 ? '已发布' : '已驳回' }}
|
||||
</span>
|
||||
{{# } }}
|
||||
</script>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="price">改价</a>
|
||||
<a class="layui-btn layui-btn-xs" lay-event="detail">查看详情</a>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'jquery', 'layer'], function () {
|
||||
var table = layui.table, $ = layui.jquery, layer = layui.layer;
|
||||
table.render({
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
<script>
|
||||
layui.use(['table', 'form', 'layer', 'jquery', 'http'], function () {
|
||||
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.jquery, http = layui.http;
|
||||
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: '{:url("/appmall/backend/addonreview/index")}',
|
||||
url: 'index',
|
||||
page: true,
|
||||
response: { statusName: 'code', msgName: 'message', countName: 'count', dataName: 'data' },
|
||||
parseData: function (res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
},
|
||||
where: { status: $('#statusFilter').val() },
|
||||
limits: [10, 20, 50],
|
||||
limit: 10,
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 70},
|
||||
{field: 'title', title: '插件名称', width: 160},
|
||||
{field: 'name', title: '标识', width: 140},
|
||||
{field: 'version', title: '版本', width: 90},
|
||||
{field: 'developer_id', title: '开发者ID', width: 100},
|
||||
{field: 'price', title: '价格', width: 90, templet: function(d){return d.price ? d.price : '免费';}},
|
||||
{field: 'status', title: '状态', width: 90, templet: function(d){
|
||||
if(d.status==0) return '<span class="layui-badge">待审核</span>';
|
||||
if(d.status==1) return '<span class="layui-badge layui-bg-green">已发布</span>';
|
||||
return '<span class="layui-badge layui-bg-red">已驳回</span>';
|
||||
}},
|
||||
{field: 'reject_reason', title: '驳回原因', width: 160},
|
||||
{field: 'create_at', title: '提交时间', width: 170, templet: function(d){return layui.util.toDateString(d.create_at*1000);}},
|
||||
{title: '操作', width: 160, templet: '#toolTpl', fixed: 'right'}
|
||||
]]
|
||||
});
|
||||
{ field: 'id', title: 'ID', width: 70 },
|
||||
{ field: 'name', title: '标识', width: 120 },
|
||||
{ field: 'type', title: '类型', width: 80, templet: function (d) {
|
||||
return d.type === 'template'
|
||||
? '<span class="layui-badge" style="background:#722ed1;">模板</span>'
|
||||
: '<span class="layui-badge layui-bg-blue">插件</span>';
|
||||
} },
|
||||
{ field: 'title', title: '名称', minWidth: 140 },
|
||||
{ field: 'author', title: '作者', width: 120 },
|
||||
{ field: 'version', title: '版本', width: 90 },
|
||||
{ field: 'price', title: '价格', width: 90, templet: function (d) { return d.price > 0 ? '¥' + d.price : '免费'; } },
|
||||
{ field: 'status', title: '状态', width: 90, templet: '#statusTpl' },
|
||||
{ field: 'reject_reason', title: '驳回原因', minWidth: 160 },
|
||||
{ field: 'create_at', title: '提交时间', width: 170, templet: function (d) { return d.create_at ? new Date(d.create_at * 1000).toLocaleString() : ''; } },
|
||||
{ title: '操作', width: 240, toolbar: '#operateTpl', fixed: 'right' }
|
||||
]],
|
||||
response: { statusName: 'code', statusCode: 0, msgName: 'message', countName: 'count', dataName: 'data' }
|
||||
});
|
||||
|
||||
$('#searchBtn').on('click', function () {
|
||||
table.reload('dataTable', { where: { status: $('#statusFilter').val() }, page: { curr: 1 } });
|
||||
});
|
||||
form.on('submit(search)', function (data) {
|
||||
table.reload('dataTable', { where: data.field, page: { curr: 1 } });
|
||||
return false;
|
||||
});
|
||||
|
||||
table.on('tool(dataTable)', function (obj) {
|
||||
var d = obj.data;
|
||||
if (obj.event === 'audit') {
|
||||
layer.confirm('确认通过该插件上架申请?', function (index) {
|
||||
$.post('{:url("/appmall/backend/addonreview/audit")}', { id: d.id }, function (r) {
|
||||
layer.msg(r.msg || '操作完成');
|
||||
if (r.code === 0) { table.reload('dataTable'); }
|
||||
layer.close(index);
|
||||
}, 'json');
|
||||
});
|
||||
} else if (obj.event === 'reject') {
|
||||
layer.prompt({ title: '填写驳回原因', formType: 2 }, function (val, index) {
|
||||
$.post('{:url("/appmall/backend/addonreview/reject")}', { id: d.id, reason: val }, function (r) {
|
||||
layer.msg(r.msg || '操作完成');
|
||||
if (r.code === 0) { table.reload('dataTable'); }
|
||||
layer.close(index);
|
||||
}, 'json');
|
||||
});
|
||||
table.on('tool(dataTable)', function (elem) {
|
||||
var d = elem.data;
|
||||
if (elem.event === 'approve') {
|
||||
layer.confirm('确认通过该插件并发布到市场?', function () {
|
||||
http.post('approve', { id: d.id }).then(function (r) {
|
||||
if (r.code === 0) { layer.msg('已通过并发布', { icon: 1 }); table.reload('dataTable'); }
|
||||
else { layer.msg(r.message || '操作失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
});
|
||||
} else if (elem.event === 'reject') {
|
||||
layer.prompt({ title: '驳回原因', formType: 2 }, function (val, idx) {
|
||||
http.post('reject', { id: d.id, reason: val }).then(function (r) {
|
||||
if (r.code === 0) { layer.close(idx); layer.msg('已驳回', { icon: 1 }); table.reload('dataTable'); }
|
||||
else { layer.msg(r.message || '操作失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
});
|
||||
} else if (elem.event === 'detail') {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '审核详情 #' + d.id,
|
||||
area: ['720px', '80%'],
|
||||
content: 'detailview?id=' + d.id
|
||||
});
|
||||
} else if (elem.event === 'price') {
|
||||
layer.prompt({ title: '修改「' + d.name + '」价格(0 为免费,同步全部版本)', value: d.price || '0' }, function (val, idx) {
|
||||
var p = parseFloat(val);
|
||||
if (isNaN(p) || p < 0) { layer.msg('请输入不小于 0 的价格', { icon: 2 }); return; }
|
||||
http.post('updatePrice', { name: d.name, price: p }).then(function (r) {
|
||||
if (r.code === 0) { layer.close(idx); layer.msg(r.message || '价格已更新', { icon: 1 }); table.reload('dataTable'); }
|
||||
else { layer.msg(r.message || '操作失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
<!--
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Description: 开发者中心控制台(Discuz! 应用中心风格:顶栏 + 左侧菜单 + 内容区)
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>开发者中心 - YwxApp 应用市场</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--brand: #1677ff;
|
||||
--brand-dark: #0e5fd8;
|
||||
--bg: #f0f2f5;
|
||||
--card: #fff;
|
||||
--text: #1f2329;
|
||||
--muted: #8a919f;
|
||||
--line: #ebedf0;
|
||||
--ok: #18a058;
|
||||
--warn: #d48806;
|
||||
--danger: #d4380d;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--bg); color: var(--text); font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; font-size: 14px; }
|
||||
a { color: var(--brand); text-decoration: none; }
|
||||
|
||||
/* 顶栏 */
|
||||
.topbar { height: 56px; background: #fff; border-bottom: 1px solid var(--line); display: flex; align-items: center; padding: 0 22px; position: sticky; top: 0; z-index: 10; }
|
||||
.topbar .brand { font-size: 17px; font-weight: 700; color: var(--text); display: flex; align-items: center; gap: 8px; }
|
||||
.topbar .brand .logo { width: 26px; height: 26px; border-radius: 6px; background: linear-gradient(135deg, var(--brand), #36cfc9); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-size: 15px; }
|
||||
.topbar .brand small { font-weight: 400; color: var(--muted); font-size: 13px; margin-left: 2px; }
|
||||
.topbar .spacer { flex: 1; }
|
||||
.topbar .who { color: var(--muted); font-size: 13px; margin-right: 14px; }
|
||||
.topbar .who b { color: var(--text); }
|
||||
|
||||
/* 布局 */
|
||||
.layout { display: flex; min-height: calc(100vh - 56px); }
|
||||
.sidebar { width: 210px; background: #fff; border-right: 1px solid var(--line); padding: 14px 0; flex-shrink: 0; }
|
||||
.sidebar .menu { list-style: none; margin: 0; padding: 0; }
|
||||
.sidebar .menu li { padding: 12px 22px; cursor: pointer; color: #4e5969; display: flex; align-items: center; gap: 10px; font-size: 14px; border-left: 3px solid transparent; transition: all .15s; }
|
||||
.sidebar .menu li .ic { font-size: 16px; width: 18px; text-align: center; }
|
||||
.sidebar .menu li:hover { background: #f7f9fc; color: var(--brand); }
|
||||
.sidebar .menu li.active { background: #eef4ff; color: var(--brand); border-left-color: var(--brand); font-weight: 600; }
|
||||
|
||||
.content { flex: 1; padding: 22px 26px; min-width: 0; }
|
||||
.panel { display: none; }
|
||||
.panel.active { display: block; animation: fade .2s; }
|
||||
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
|
||||
|
||||
.page-title { font-size: 18px; font-weight: 700; margin: 0 0 4px; }
|
||||
.page-sub { color: var(--muted); margin: 0 0 18px; font-size: 13px; }
|
||||
|
||||
/* 卡片/统计 */
|
||||
.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; }
|
||||
.stat-card { background: var(--card); border-radius: 10px; padding: 16px 18px; box-shadow: 0 1px 3px rgba(0,0,0,.04); }
|
||||
.stat-card .lbl { color: var(--muted); font-size: 13px; }
|
||||
.stat-card .num { font-size: 26px; font-weight: 700; margin-top: 6px; }
|
||||
.stat-card .num.ok { color: var(--ok); }
|
||||
.stat-card .num.brand { color: var(--brand); }
|
||||
|
||||
.card { background: var(--card); border-radius: 10px; padding: 18px 20px; box-shadow: 0 1px 3px rgba(0,0,0,.04); margin-bottom: 16px; }
|
||||
.card h3 { margin: 0 0 14px; font-size: 15px; display: flex; align-items: center; gap: 8px; }
|
||||
.card h3 .bar { width: 4px; height: 15px; background: var(--brand); border-radius: 2px; display: inline-block; }
|
||||
|
||||
/* 表格 */
|
||||
table.dev-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
table.dev-table th, table.dev-table td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--line); }
|
||||
table.dev-table th { color: var(--muted); font-weight: 500; background: #fafbfc; }
|
||||
table.dev-table tr:hover td { background: #fafcff; }
|
||||
.app-name { font-weight: 600; }
|
||||
.app-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||
|
||||
.tag { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 12px; }
|
||||
.tag.up { background: #e8f7ee; color: var(--ok); }
|
||||
.tag.down { background: #f1f2f4; color: var(--muted); }
|
||||
.tag.wait { background: #fff7e6; color: var(--warn); }
|
||||
.tag.reject { background: #fff1f0; color: var(--danger); }
|
||||
.tag.free { background: #eef4ff; color: var(--brand); }
|
||||
.tag.t-addon { background: #eef4ff; color: var(--brand); }
|
||||
.tag.t-tpl { background: #f4eefe; color: #722ed1; }
|
||||
|
||||
.empty { text-align: center; color: var(--muted); padding: 40px 0; }
|
||||
|
||||
/* 令牌闸门 */
|
||||
.gate { max-width: 420px; margin: 12vh auto 0; background: #fff; border-radius: 12px; padding: 34px 36px; box-shadow: 0 4px 20px rgba(0,0,0,.08); }
|
||||
.gate h2 { margin: 0 0 6px; text-align: center; }
|
||||
.gate p { text-align: center; color: var(--muted); margin: 0 0 22px; font-size: 13px; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.layui-btn { border-radius: 6px; }
|
||||
.toolbar { margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
|
||||
.copybox { background: #f6f8fa; border: 1px dashed #d0d7de; border-radius: 6px; padding: 10px 12px; font-family: monospace; word-break: break-all; margin: 8px 0; }
|
||||
.kv { display: flex; padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
|
||||
.kv:last-child { border-bottom: none; }
|
||||
.kv .k { width: 110px; color: var(--muted); flex-shrink: 0; }
|
||||
.kv .v { flex: 1; word-break: break-all; }
|
||||
.hint { color: var(--muted); font-size: 12px; margin-top: 6px; }
|
||||
.hidden { display: none !important; }
|
||||
.act-link { color: var(--brand); cursor: pointer; }
|
||||
@media (max-width: 880px) { .stat-row { grid-template-columns: repeat(2, 1fr); } .sidebar { width: 64px; } .sidebar .menu li span.txt { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶栏 -->
|
||||
<div class="topbar" id="topbar">
|
||||
<div class="brand"><span class="logo">Y</span>YwxApp 应用市场<small>开发者中心</small></div>
|
||||
<div class="spacer"></div>
|
||||
<div class="who" id="who"></div>
|
||||
<a href="javascript:;" class="layui-btn layui-btn-sm layui-btn-primary" id="logoutBtn">退出</a>
|
||||
</div>
|
||||
|
||||
<!-- 令牌闸门 -->
|
||||
<div id="gate" class="gate hidden">
|
||||
<h2>开发者令牌登录</h2>
|
||||
<p>请输入你的开发者令牌以进入控制台(可在「查询我的令牌」页取回)</p>
|
||||
<form class="layui-form" id="gateForm">
|
||||
<div class="layui-form-item">
|
||||
<input type="text" name="token" id="gateToken" required lay-verify="required" placeholder="粘贴开发者令牌" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<button class="layui-btn layui-btn-fluid layui-btn-normal" lay-submit lay-filter="gate">进入控制台</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="hint" style="text-align:center;">
|
||||
还没有令牌?<a href="{:url('index')}">立即申请入驻</a> ·
|
||||
<a href="{:url('mine')}">找回令牌</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 控制台主体 -->
|
||||
<div class="layout hidden" id="console">
|
||||
<div class="sidebar">
|
||||
<ul class="menu" id="menu">
|
||||
<li data-tab="overview" class="active"><span class="ic layui-icon layui-icon-console"></span><span class="txt">概览</span></li>
|
||||
<li data-tab="apps"><span class="ic layui-icon layui-icon-app"></span><span class="txt">我的应用</span></li>
|
||||
<li data-tab="submit"><span class="ic layui-icon layui-icon-upload"></span><span class="txt">提交应用</span></li>
|
||||
<li data-tab="earn"><span class="ic layui-icon layui-icon-rmb"></span><span class="txt">收益账单</span></li>
|
||||
<li data-tab="withdraw"><span class="ic layui-icon layui-icon-transfer"></span><span class="txt">提现</span></li>
|
||||
<li data-tab="account"><span class="ic layui-icon layui-icon-user"></span><span class="txt">账户设置</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
||||
<!-- 概览 -->
|
||||
<section class="panel active" id="panel-overview">
|
||||
<h2 class="page-title">概览</h2>
|
||||
<p class="page-sub">你的应用与市场表现一览</p>
|
||||
<div class="stat-row" id="statRow">
|
||||
<div class="stat-card"><div class="lbl">已上架应用</div><div class="num brand" id="stApps">-</div></div>
|
||||
<div class="stat-card"><div class="lbl">待审核提交</div><div class="num warn" id="stPending">-</div></div>
|
||||
<div class="stat-card"><div class="lbl">总下载量</div><div class="num" id="stDl">-</div></div>
|
||||
<div class="stat-card"><div class="lbl">可提现余额</div><div class="num ok" id="stBalance">-</div></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="bar"></span>近期提交</h3>
|
||||
<table class="dev-table">
|
||||
<thead><tr><th>应用</th><th>版本</th><th>状态</th><th>时间</th></tr></thead>
|
||||
<tbody id="recentBody"><tr><td colspan="4" class="empty">加载中…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 我的应用 -->
|
||||
<section class="panel" id="panel-apps">
|
||||
<h2 class="page-title">我的应用</h2>
|
||||
<p class="page-sub">查看你已上架/下架的应用,以及待审核的提交记录</p>
|
||||
<div class="toolbar">
|
||||
<span class="hint">如需发布新版本,请前往「提交应用」上传 zip 包</span>
|
||||
<a href="javascript:;" class="layui-btn layui-btn-sm layui-btn-normal" data-goto="submit">+ 提交新应用</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="bar"></span>已上架 / 下架应用</h3>
|
||||
<table class="dev-table">
|
||||
<thead><tr><th>应用</th><th>版本</th><th>价格</th><th>下载</th><th>状态</th><th>更新时间</th><th>操作</th></tr></thead>
|
||||
<tbody id="listedBody"><tr><td colspan="7" class="empty">加载中…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="bar"></span>提交记录</h3>
|
||||
<table class="dev-table">
|
||||
<thead><tr><th>应用</th><th>版本</th><th>状态</th><th>说明</th><th>更新时间</th></tr></thead>
|
||||
<tbody id="subBody"><tr><td colspan="5" class="empty">加载中…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 提交应用 -->
|
||||
<section class="panel" id="panel-submit">
|
||||
<h2 class="page-title">商品提交</h2>
|
||||
<p class="page-sub" id="submitSub">上传插件 zip 包(根目录需含 info.php),提交后等待官方审核</p>
|
||||
<div class="card">
|
||||
<form class="layui-form" id="submitForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">应用类型</label>
|
||||
<div class="layui-input-inline" style="width:200px;">
|
||||
<select name="type" id="appType">
|
||||
<option value="addon">插件</option>
|
||||
<option value="template">模板</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">插件含 info.php;模板含 templates/名称/template.json</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">插件标识</label>
|
||||
<div class="layui-input-inline" style="width:260px;">
|
||||
<input type="text" name="name" required lay-verify="required" placeholder="如 mqttbroker(仅字母数字下划线)" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux" id="nameHint">与 info.php 中 name 一致</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">版本号</label>
|
||||
<div class="layui-input-inline" style="width:200px;">
|
||||
<input type="text" name="version" required lay-verify="required" placeholder="如 1.0.0" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">格式 x.y.z,且未重复提交</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">应用名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" required lay-verify="required" placeholder="如 MQTT 消息代理" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">作者</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="author" placeholder="开发者/工作室名称" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类</label>
|
||||
<div class="layui-input-inline" style="width:200px;">
|
||||
<select name="category">
|
||||
<option value="工具">工具</option>
|
||||
<option value="电商">电商</option>
|
||||
<option value="社交">社交</option>
|
||||
<option value="内容">内容</option>
|
||||
<option value="教育">教育</option>
|
||||
<option value="其他">其他</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="layui-form-label">价格(¥)</label>
|
||||
<div class="layui-input-inline" style="width:140px;">
|
||||
<input type="number" name="price" value="0" step="0.01" min="0" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">标签</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="tags" placeholder="逗号分隔,如 物联网,通信" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">封面图URL</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="logo" placeholder="可选,应用封面图地址" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">简介</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="description" placeholder="应用简介/功能描述" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">更新日志</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="changelog" placeholder="如:1.0.1 修复某某问题;1.0.0 首次发布" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">最低框架版本</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="require_framework" placeholder="如 1.2.0(留空表示不限)" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">插件包</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="file" name="file" id="pkgFile" accept=".zip" required lay-verify="required" class="layui-input">
|
||||
<div class="hint" id="pkgHint">仅支持 .zip,插件包根目录需含 info.php,将进行安全扫描</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="doSubmit">提交审核</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 收益账单 -->
|
||||
<section class="panel" id="panel-earn">
|
||||
<h2 class="page-title">收益账单</h2>
|
||||
<p class="page-sub">查看销售收益与结算情况</p>
|
||||
<div class="stat-row">
|
||||
<div class="stat-card"><div class="lbl">可提现余额</div><div class="num ok" id="eaBalance">¥0.00</div></div>
|
||||
<div class="stat-card"><div class="lbl">冻结中(提现待打款)</div><div class="num warn" id="eaFrozen">¥0.00</div></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="bar"></span>收益明细</h3>
|
||||
<table class="dev-table">
|
||||
<thead><tr><th>订单号</th><th>销售额</th><th>抽成</th><th>应得</th><th>状态</th><th>时间</th></tr></thead>
|
||||
<tbody id="revBody"><tr><td colspan="6" class="empty">加载中…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 提现 -->
|
||||
<section class="panel" id="panel-withdraw">
|
||||
<h2 class="page-title">提现</h2>
|
||||
<p class="page-sub">将可提现余额提现至你的收款账号</p>
|
||||
<div class="card" style="max-width:560px;">
|
||||
<form class="layui-form" id="withdrawForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">提现金额</label>
|
||||
<div class="layui-input-inline" style="width:200px;">
|
||||
<input type="number" name="amount" step="0.01" min="0.01" required lay-verify="required" placeholder="0.00" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">不可超过可提现余额</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">提现渠道</label>
|
||||
<div class="layui-input-inline" style="width:200px;">
|
||||
<select name="channel">
|
||||
<option value="alipay">支付宝</option>
|
||||
<option value="wechat">微信</option>
|
||||
<option value="bank">银行卡</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">收款账号</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="account" required lay-verify="required" placeholder="支付宝/微信账号或银行卡号" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="doWithdraw">提交提现申请</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 账户设置 -->
|
||||
<section class="panel" id="panel-account">
|
||||
<h2 class="page-title">账户设置</h2>
|
||||
<p class="page-sub">管理你的开发者身份与令牌</p>
|
||||
<div class="card" style="max-width:620px;">
|
||||
<div class="kv"><div class="k">开发者名称</div><div class="v" id="acName">-</div></div>
|
||||
<div class="kv"><div class="k">邮箱</div><div class="v" id="acEmail">-</div></div>
|
||||
<div class="kv"><div class="k">账户状态</div><div class="v" id="acStatus">-</div></div>
|
||||
<div class="kv"><div class="k">开发者令牌</div><div class="v">
|
||||
<div class="copybox" id="acToken">-</div>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-normal" id="copyToken">复制令牌</button>
|
||||
<span class="hint">发布插件到官方市场时需使用此令牌(APPMARKET_DEV_TOKEN)。</span>
|
||||
</div></div>
|
||||
<div class="kv"><div class="k">操作</div><div class="v">
|
||||
<a href="{:url('mine')}">丢失令牌?前往找回</a>
|
||||
</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
<script>
|
||||
layui.use(['form', 'jquery', 'layer', 'http'], function () {
|
||||
var form = layui.form, $ = layui.jquery, layer = layui.layer, http = layui.http;
|
||||
var TOKEN_KEY = 'appmall_dev_token';
|
||||
var token = '';
|
||||
|
||||
// ---- 工具 ----
|
||||
function getParam(name) {
|
||||
var m = location.search.match(new RegExp('[?&]' + name + '=([^&]*)'));
|
||||
return m ? decodeURIComponent(m[1]) : '';
|
||||
}
|
||||
function maskToken(t) {
|
||||
if (!t || t.length < 12) return t;
|
||||
return t.substr(0, 6) + '****' + t.substr(-4);
|
||||
}
|
||||
function money(v) { return '¥' + (parseFloat(v || 0)).toFixed(2); }
|
||||
function fmtTime(ts) {
|
||||
if (!ts) return '-';
|
||||
var d = new Date(ts * 1000);
|
||||
return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2)
|
||||
+ ' ' + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2);
|
||||
}
|
||||
// Result 系接口成功 code=0;Market::submit 成功 code=1
|
||||
function req(url, data, successCode) {
|
||||
successCode = (successCode === undefined) ? 0 : successCode;
|
||||
return http.post(url, data).then(function (r) {
|
||||
if (r && r.code === successCode) return r;
|
||||
return Promise.reject((r && r.message) || (r && r.msg) || '请求失败');
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 令牌闸门 ----
|
||||
function showGate() { $('#gate').removeClass('hidden'); $('#console').addClass('hidden'); $('#topbar').addClass('hidden'); }
|
||||
function enterConsole(tk) {
|
||||
token = tk;
|
||||
try { localStorage.setItem(TOKEN_KEY, tk); } catch (e) {}
|
||||
$('#gate').addClass('hidden');
|
||||
$('#topbar').removeClass('hidden');
|
||||
$('#console').removeClass('hidden');
|
||||
loadOverview();
|
||||
loadAccount();
|
||||
}
|
||||
|
||||
// 初始化:URL > localStorage
|
||||
var initToken = getParam('token') || (function () { try { return localStorage.getItem(TOKEN_KEY) || ''; } catch (e) { return ''; } })();
|
||||
if (!initToken) {
|
||||
showGate();
|
||||
} else {
|
||||
// 校验令牌有效性
|
||||
req('/appmall/developer/overview', { token: initToken }).then(function (r) {
|
||||
enterConsole(initToken);
|
||||
}).catch(function (msg) {
|
||||
showGate();
|
||||
layer.msg(msg || '令牌无效', { icon: 2 });
|
||||
});
|
||||
}
|
||||
|
||||
form.on('submit(gate)', function (data) {
|
||||
var tk = (data.field.token || '').trim();
|
||||
if (!tk) { layer.msg('请输入令牌', { icon: 2 }); return false; }
|
||||
layer.load(1);
|
||||
req('/appmall/developer/overview', { token: tk }).then(function () {
|
||||
layer.closeAll();
|
||||
enterConsole(tk);
|
||||
}).catch(function (msg) {
|
||||
layer.closeAll();
|
||||
layer.msg(msg || '令牌无效', { icon: 2 });
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#logoutBtn').on('click', function () {
|
||||
try { localStorage.removeItem(TOKEN_KEY); } catch (e) {}
|
||||
location.href = '{:url("index")}';
|
||||
});
|
||||
|
||||
// ---- 左侧菜单 ----
|
||||
$('#menu').on('click', 'li', function () {
|
||||
var tab = $(this).data('tab');
|
||||
$('#menu li').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$('.panel').removeClass('active');
|
||||
$('#panel-' + tab).addClass('active');
|
||||
if (tab === 'overview') loadOverview();
|
||||
else if (tab === 'apps') loadApps();
|
||||
else if (tab === 'earn') loadEarnings();
|
||||
else if (tab === 'account') loadAccount();
|
||||
});
|
||||
$('[data-goto]').on('click', function () {
|
||||
var tab = $(this).data('goto');
|
||||
$('#menu li[data-tab="' + tab + '"]').click();
|
||||
});
|
||||
|
||||
// ---- 概览 ----
|
||||
function loadOverview() {
|
||||
req('/appmall/developer/overview', { token: token }).then(function (r) {
|
||||
var d = r.data || {};
|
||||
$('#who').html('欢迎,<b>' + (d.username || '开发者') + '</b>');
|
||||
$('#stApps').text(d.app_count || 0);
|
||||
$('#stPending').text(d.pending_count || 0);
|
||||
$('#stDl').text(d.total_downloads || 0);
|
||||
$('#stBalance').text(money(d.balance));
|
||||
var rows = d.recent || [];
|
||||
if (!rows.length) {
|
||||
$('#recentBody').html('<tr><td colspan="4" class="empty">暂无提交记录</td></tr>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
rows.forEach(function (it) {
|
||||
var st = subStatus(it.status);
|
||||
html += '<tr><td class="app-name">' + esc(it.title) + typeTag(it.type) + '</td><td>' + esc(it.version) + '</td><td>' + st + '</td><td>' + fmtTime(it.update_at) + '</td></tr>';
|
||||
});
|
||||
$('#recentBody').html(html);
|
||||
}).catch(function (msg) { layer.msg(msg, { icon: 2 }); });
|
||||
}
|
||||
|
||||
// ---- 我的应用 ----
|
||||
function loadApps() {
|
||||
req('/appmall/developer/apps', { token: token }).then(function (r) {
|
||||
var d = r.data || {};
|
||||
var listed = d.listed || [], subs = d.submissions || [];
|
||||
if (!listed.length) {
|
||||
$('#listedBody').html('<tr><td colspan="7" class="empty">你还没有已上架的应用</td></tr>');
|
||||
} else {
|
||||
var h = '';
|
||||
listed.forEach(function (it) {
|
||||
var price = (parseFloat(it.price) > 0) ? money(it.price) : '<span class="tag free">免费</span>';
|
||||
var st = it.status == 1
|
||||
? '<span class="tag up">上架中</span>'
|
||||
: '<span class="tag down">已下架</span>';
|
||||
h += '<tr>'
|
||||
+ '<td class="app-name">' + esc(it.title) + typeTag(it.type) + '<div class="app-meta">' + esc(it.name) + (it.category ? ' · ' + esc(it.category) : '') + '</div></td>'
|
||||
+ '<td>' + esc(it.version) + '</td>'
|
||||
+ '<td>' + price + '</td>'
|
||||
+ '<td>' + (it.download_count || 0) + '</td>'
|
||||
+ '<td>' + st + '</td>'
|
||||
+ '<td>' + fmtTime(it.update_at) + '</td>'
|
||||
+ '<td><a class="act-link" data-act="price" data-name="' + esc(it.name) + '" data-price="' + (it.price || 0) + '">改价</a></td></tr>';
|
||||
});
|
||||
$('#listedBody').html(h);
|
||||
}
|
||||
if (!subs.length) {
|
||||
$('#subBody').html('<tr><td colspan="5" class="empty">暂无提交记录</td></tr>');
|
||||
} else {
|
||||
var h2 = '';
|
||||
subs.forEach(function (it) {
|
||||
var st = subStatus(it.status);
|
||||
var note = it.status == 2 ? (it.reject_reason ? esc(it.reject_reason) : '已驳回,可重新提交') : '-';
|
||||
h2 += '<tr>'
|
||||
+ '<td class="app-name">' + esc(it.title) + typeTag(it.type) + '<div class="app-meta">' + esc(it.name) + '</div></td>'
|
||||
+ '<td>' + esc(it.version) + '</td>'
|
||||
+ '<td>' + st + '</td>'
|
||||
+ '<td>' + note + '</td>'
|
||||
+ '<td>' + fmtTime(it.update_at) + '</td></tr>';
|
||||
});
|
||||
$('#subBody').html(h2);
|
||||
}
|
||||
}).catch(function (msg) { layer.msg(msg, { icon: 2 }); });
|
||||
}
|
||||
|
||||
// 改价(无需重新打包/重新提审,同步该应用全部版本,市场即时生效)
|
||||
$('#listedBody').on('click', '[data-act="price"]', function () {
|
||||
var name = $(this).data('name');
|
||||
var cur = $(this).data('price') || 0;
|
||||
layer.prompt({ title: '修改「' + name + '」价格(0 为免费,同步全部版本)', value: String(cur) }, function (val, idx) {
|
||||
var p = parseFloat(val);
|
||||
if (isNaN(p) || p < 0) { layer.msg('请输入不小于 0 的价格', { icon: 2 }); return; }
|
||||
req('/appmall/developer/price', { token: token, name: name, price: p }).then(function (r) {
|
||||
layer.close(idx);
|
||||
layer.msg(r.message || '价格已更新', { icon: 1 });
|
||||
loadApps();
|
||||
}).catch(function (msg) { layer.msg(msg, { icon: 2 }); });
|
||||
});
|
||||
});
|
||||
|
||||
function subStatus(s) {
|
||||
if (s == 0) return '<span class="tag wait">待审核</span>';
|
||||
if (s == 1) return '<span class="tag up">已通过</span>';
|
||||
if (s == 2) return '<span class="tag reject">已驳回</span>';
|
||||
return '<span class="tag down">未知</span>';
|
||||
}
|
||||
function typeTag(t) {
|
||||
return t === 'template'
|
||||
? ' <span class="tag t-tpl">模板</span>'
|
||||
: ' <span class="tag t-addon">插件</span>';
|
||||
}
|
||||
// 提交类型切换:动态改写包要求与标识提示
|
||||
$('#appType').on('change', function () {
|
||||
var isTpl = $(this).val() === 'template';
|
||||
$('#submitSub').text(isTpl
|
||||
? '上传模板 zip 包(须含 templates/名称/template.json),提交后等待官方审核'
|
||||
: '上传插件 zip 包(根目录需含 info.php),提交后等待官方审核');
|
||||
$('#pkgHint').text(isTpl
|
||||
? '仅支持 .zip,模板包须含 templates/<名称>/template.json,将进行安全扫描(模板包禁含 PHP 文件)'
|
||||
: '仅支持 .zip,插件包根目录需含 info.php,将进行安全扫描');
|
||||
$('#nameHint').text(isTpl ? '与 template.json 中标识(文件夹名)一致' : '与 info.php 中 name 一致');
|
||||
});
|
||||
|
||||
// ---- 提交应用 ----
|
||||
form.on('submit(doSubmit)', function () {
|
||||
var file = document.getElementById('pkgFile').files[0];
|
||||
if (!file) { layer.msg('请选择插件 zip 包', { icon: 2 }); return false; }
|
||||
var fd = new FormData();
|
||||
fd.append('file', file);
|
||||
fd.append('token', token);
|
||||
fd.append('type', $('select[name="type"]').val());
|
||||
fd.append('name', $('input[name="name"]').val());
|
||||
fd.append('version', $('input[name="version"]').val());
|
||||
fd.append('title', $('input[name="title"]').val());
|
||||
fd.append('author', $('input[name="author"]').val());
|
||||
fd.append('category', $('select[name="category"]').val());
|
||||
fd.append('tags', $('input[name="tags"]').val());
|
||||
fd.append('price', $('input[name="price"]').val());
|
||||
fd.append('logo', $('input[name="logo"]').val());
|
||||
fd.append('description', $('textarea[name="description"]').val());
|
||||
fd.append('changelog', $('textarea[name="changelog"]').val());
|
||||
fd.append('require_framework', $('input[name="require_framework"]').val());
|
||||
layer.load(1);
|
||||
http.upload('/appmall/api/addon/submit', file, {
|
||||
token: token,
|
||||
type: $('select[name="type"]').val(),
|
||||
name: $('input[name="name"]').val(),
|
||||
version: $('input[name="version"]').val(),
|
||||
title: $('input[name="title"]').val(),
|
||||
author: $('input[name="author"]').val(),
|
||||
category: $('select[name="category"]').val(),
|
||||
tags: $('input[name="tags"]').val(),
|
||||
price: $('input[name="price"]').val(),
|
||||
logo: $('input[name="logo"]').val(),
|
||||
description: $('textarea[name="description"]').val()
|
||||
}).then(function (r) {
|
||||
layer.closeAll();
|
||||
if (r && r.code === 1) {
|
||||
layer.msg(r.msg || '提交成功,等待审核', { icon: 1 });
|
||||
$('#submitForm')[0].reset();
|
||||
form.render();
|
||||
} else {
|
||||
layer.msg((r && (r.msg || r.message)) || '提交失败', { icon: 2 });
|
||||
}
|
||||
}).catch(function () { layer.closeAll(); layer.msg('请求失败', { icon: 2 }); });
|
||||
return false;
|
||||
});
|
||||
|
||||
// ---- 收益 ----
|
||||
function loadEarnings() {
|
||||
req('/appmall/developer/earnings', { token: token }).then(function (r) {
|
||||
var d = r.data || {};
|
||||
$('#eaBalance').text(money(d.balance));
|
||||
$('#eaFrozen').text(money(d.frozen_balance));
|
||||
var list = d.revenues || [];
|
||||
if (!list.length) {
|
||||
$('#revBody').html('<tr><td colspan="6" class="empty">暂无收益记录</td></tr>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
list.forEach(function (it) {
|
||||
var st = it.status == 1 ? '<span style="color:#18a058;">已结算</span>' : '<span style="color:#d48806;">待结算</span>';
|
||||
html += '<tr>'
|
||||
+ '<td>' + (it.order_id || '-') + '</td>'
|
||||
+ '<td>' + money(it.amount) + '</td>'
|
||||
+ '<td>' + money(it.commission) + '</td>'
|
||||
+ '<td>' + money(it.income) + '</td>'
|
||||
+ '<td>' + st + '</td>'
|
||||
+ '<td>' + fmtTime(it.create_at) + '</td></tr>';
|
||||
});
|
||||
$('#revBody').html(html);
|
||||
}).catch(function (msg) { layer.msg(msg, { icon: 2 }); });
|
||||
}
|
||||
|
||||
// ---- 提现 ----
|
||||
form.on('submit(doWithdraw)', function (data) {
|
||||
data.field.token = token;
|
||||
var amt = parseFloat(data.field.amount);
|
||||
if (!(amt > 0)) { layer.msg('请输入正确的提现金额', { icon: 2 }); return false; }
|
||||
req('/appmall/developer/withdraw', data.field).then(function (r) {
|
||||
layer.msg(r.message || '提交成功', { icon: 1 });
|
||||
$('#withdrawForm')[0].reset(); form.render();
|
||||
loadEarnings();
|
||||
}).catch(function (msg) { layer.msg(msg, { icon: 2 }); });
|
||||
return false;
|
||||
});
|
||||
|
||||
// ---- 账户 ----
|
||||
function loadAccount() {
|
||||
req('/appmall/developer/overview', { token: token }).then(function (r) {
|
||||
var d = r.data || {};
|
||||
$('#acName').text(d.username || '-');
|
||||
$('#acEmail').text(d.email || '-');
|
||||
$('#acStatus').html('<span class="tag up">正常</span>');
|
||||
$('#acToken').text(token);
|
||||
}).catch(function () {});
|
||||
}
|
||||
$('#copyToken').on('click', function () {
|
||||
var t = document.getElementById('acToken').innerText;
|
||||
var ta = $('<textarea>').val(t).appendTo('body').select();
|
||||
try { document.execCommand('copy'); layer.msg('已复制', { icon: 1 }); } catch (e) { layer.msg('复制失败', { icon: 2 }); }
|
||||
ta.remove();
|
||||
});
|
||||
|
||||
function esc(s) {
|
||||
return $('<div>').text(s == null ? '' : s).html();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
<!--
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-07-21 13:40:36
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-07-26 15:36:01
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\addon\appmall\view\frontend\developer\mine.html
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>查询我的令牌 - YwxApp 应用市场</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f2f3f5; margin: 0; }
|
||||
.topbar { height: 52px; background: #fff; border-bottom: 1px solid #ebedf0; display: flex; align-items: center; padding: 0 22px; }
|
||||
.topbar .brand { font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||
.topbar .brand .logo { width: 24px; height: 24px; border-radius: 6px; background: linear-gradient(135deg, #1677ff, #36cfc9); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; }
|
||||
.topbar .brand small { font-weight: 400; color: #8a919f; font-size: 13px; }
|
||||
.topbar .spacer { flex: 1; }
|
||||
.dev-box { max-width: 460px; margin: 48px auto; background: #fff; padding: 30px 35px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,.08); }
|
||||
.dev-box h2 { text-align: center; margin-bottom: 6px; }
|
||||
.dev-tip { text-align: center; color: #999; margin-bottom: 22px; font-size: 13px; }
|
||||
.dev-token { background: #f4f4f4; padding: 12px; border-radius: 6px; word-break: break-all; font-family: monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<div class="brand"><span class="logo">Y</span>YwxApp 应用市场<small>开发者中心</small></div>
|
||||
<div class="spacer"></div>
|
||||
<a href="{:url('console')}" class="layui-btn layui-btn-sm layui-btn-normal">进入开发者中心</a>
|
||||
</div>
|
||||
<div class="dev-box">
|
||||
<h2>查询我的令牌</h2>
|
||||
<p class="dev-tip">输入注册邮箱,取回你的开发者令牌(若已激活将重新邮件发送)</p>
|
||||
|
||||
<form class="layui-form" id="lookupForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">邮箱</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="email" name="email" required lay-verify="email" placeholder="注册时填写的邮箱" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="lookup">查询令牌</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="result" style="display:none; margin-top:20px;">
|
||||
<div id="resultMsg" class="layui-bg-blue" style="padding:10px 12px;border-radius:6px;margin-bottom:12px;"></div>
|
||||
<div id="tokenWrap" style="display:none;">
|
||||
<label class="layui-form-label" style="text-align:left;padding-left:0;">你的开发者令牌</label>
|
||||
<div class="dev-token" id="tokenVal"></div>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-normal" id="copyBtn" style="margin-top:10px;">复制令牌</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="dev-tip" style="margin-top:18px;"><a href="{:url('index')}">返回开发者注册</a></p>
|
||||
</div>
|
||||
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
<script>
|
||||
layui.use(['form', 'jquery', 'layer', 'http'], function () {
|
||||
var form = layui.form, $ = layui.jquery, layer = layui.layer, http = layui.http;
|
||||
form.on('submit(lookup)', function (data) {
|
||||
http.post('{:url("/appmall/developer/lookup")}', data.field).then(function (r) {
|
||||
$('#result').show();
|
||||
$('#resultMsg').text(r.message || '查询完成');
|
||||
if (r.code === 0 && r.data && r.data.token) {
|
||||
$('#tokenWrap').show();
|
||||
$('#tokenVal').text(r.data.token);
|
||||
} else {
|
||||
$('#tokenWrap').hide();
|
||||
}
|
||||
if (r.code !== 0) { layer.msg(r.message || '查询失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#copyBtn').on('click', function () {
|
||||
var t = document.getElementById('tokenVal').innerText;
|
||||
var dummy = $('<textarea>').val(t).appendTo('body').select();
|
||||
document.execCommand('copy');
|
||||
dummy.remove();
|
||||
layer.msg('已复制', { icon: 1 });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!--
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-07-21 13:40:29
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-07-28 12:32:45
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\addon\appmall\view\frontend\developer\register.html
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>开发者注册 - YwxApp 应用市场</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f2f3f5; margin: 0; }
|
||||
.topbar { height: 52px; background: #fff; border-bottom: 1px solid #ebedf0; display: flex; align-items: center; padding: 0 22px; }
|
||||
.topbar .brand { font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||
.topbar .brand .logo { width: 24px; height: 24px; border-radius: 6px; background: linear-gradient(135deg, #1677ff, #36cfc9); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; }
|
||||
.topbar .brand small { font-weight: 400; color: #8a919f; font-size: 13px; }
|
||||
.topbar .spacer { flex: 1; }
|
||||
.dev-box { max-width: 460px; margin: 48px auto; background: #fff; padding: 30px 35px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,.08); }
|
||||
.dev-box h2 { text-align: center; margin-bottom: 6px; }
|
||||
.dev-tip { text-align: center; color: #999; margin-bottom: 22px; font-size: 13px; }
|
||||
.dev-token { background: #f4f4f4; padding: 12px; border-radius: 6px; word-break: break-all; font-family: monospace; }
|
||||
.closed { text-align:center; color:#c00; padding:30px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<div class="brand"><span class="logo">Y</span>YwxApp 应用市场<small>开发者中心</small></div>
|
||||
<div class="spacer"></div>
|
||||
<a href="{:url('/appmall/Developer/console')}" class="layui-btn layui-btn-sm layui-btn-normal">进入开发者中心</a>
|
||||
</div>
|
||||
<div class="dev-box">
|
||||
<h2>开发者入驻</h2>
|
||||
<p class="dev-tip">注册后获取开发者令牌,用于在你自己的控制台「发布到官方市场」</p>
|
||||
|
||||
{if $register_open}
|
||||
<form class="layui-form" id="regForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">开发者名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="username" required lay-verify="required" placeholder="如:星河工作室" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">邮箱</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="email" name="email" required lay-verify="email" placeholder="用于接收令牌/审核通知" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="reg">提交申请</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="dev-tip" style="margin:0;">
|
||||
{if $need_approval}提交后需运营审核,令牌将通过邮件下发{else/}提交后令牌立即通过邮件下发{/if}
|
||||
</p>
|
||||
</form>
|
||||
<div id="result" style="display:none; margin-top:20px;">
|
||||
<div id="resultMsg" class="layui-bg-blue" style="padding:10px 12px;border-radius:6px;margin-bottom:12px;"></div>
|
||||
<div id="tokenWrap" style="display:none;">
|
||||
<label class="layui-form-label" style="text-align:left;padding-left:0;">你的开发者令牌</label>
|
||||
<div class="dev-token" id="tokenVal"></div>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-normal" id="copyBtn" style="margin-top:10px;">复制令牌</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="dev-tip" style="margin-top:18px;"><a href="{:url('mine')}">已有账号?查询我的令牌</a></p>
|
||||
{else/}
|
||||
<div class="closed">当前未开放自助注册,请联系运营获取开发者令牌。</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
<script>
|
||||
layui.use(['form', 'jquery', 'layer', 'http'], function () {
|
||||
var form = layui.form, $ = layui.jquery, layer = layui.layer, http = layui.http;
|
||||
form.on('submit(reg)', function (data) {
|
||||
http.post('{:url("/appmall/developer/save")}', data.field).then(function (r) {
|
||||
$('#result').show();
|
||||
$('#resultMsg').text(r.message || '提交完成');
|
||||
if (r.code === 0 && r.data && r.data.token) {
|
||||
$('#tokenWrap').show();
|
||||
$('#tokenVal').text(r.data.token);
|
||||
} else {
|
||||
$('#tokenWrap').hide();
|
||||
}
|
||||
if (r.code !== 0) { layer.msg(r.message || '提交失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#copyBtn').on('click', function () {
|
||||
var t = document.getElementById('tokenVal').innerText;
|
||||
var dummy = $('<textarea>').val(t).appendTo('body').select();
|
||||
document.execCommand('copy');
|
||||
dummy.remove();
|
||||
layer.msg('已复制', { icon: 1 });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user