Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
150e0ecd23 | ||
|
|
e9ed0cd8f6 | ||
|
|
187d4343b3 | ||
|
|
9bc0185b6b | ||
|
|
652b17d6a6 | ||
|
|
ed8c3d545b | ||
|
|
12b38c7bf5 | ||
|
|
fc202be987 | ||
|
|
71e069712a | ||
|
|
ca4080d5e6 | ||
|
|
4bbe287626 | ||
|
|
e316cd40e0 | ||
|
|
a7a3ddef8b | ||
|
|
51f2cbc0f4 | ||
|
|
8acf9f3f6c |
@@ -14,7 +14,7 @@ DB_PORT=3306
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_PREFIX=ea8_
|
||||
|
||||
# 限流器开关
|
||||
# 限流器开关 若启动需要配置 Redis 服务
|
||||
RATE_LIMITING_STATUS=false
|
||||
|
||||
# Redis配置
|
||||
|
||||
@@ -213,4 +213,23 @@ class Ajax extends AdminController
|
||||
return json($config);
|
||||
}
|
||||
}
|
||||
|
||||
public function composerInfo(): Json
|
||||
{
|
||||
$lockFilePath = root_path() . '/composer.lock';
|
||||
$list = [];
|
||||
if (file_exists($lockFilePath)) {
|
||||
$lockFileContent = file_get_contents($lockFilePath);
|
||||
if ($lockFileContent !== false) {
|
||||
$lockData = json_decode($lockFileContent, true);
|
||||
if (!empty($lockData['packages'])) {
|
||||
foreach ($lockData['packages'] as $package) {
|
||||
$list[] = ['name' => $package['name'], 'version' => $package['version']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->success('success', $list);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,8 @@ use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\response\Json;
|
||||
use Wolfcode\Ai\Enum\AiType;
|
||||
use Wolfcode\Ai\Service\AiChatService;
|
||||
|
||||
#[ControllerAnnotation(title: '商城商品管理')]
|
||||
class Goods extends AdminController
|
||||
@@ -72,4 +74,62 @@ class Goods extends AdminController
|
||||
{
|
||||
return '这里演示方法不需要经过登录验证';
|
||||
}
|
||||
|
||||
|
||||
#[NodeAnnotation(title: 'AI优化', auth: true)]
|
||||
public function aiOptimization(Request $request): void
|
||||
{
|
||||
$message = $request->post('message');
|
||||
if (empty($message)) $this->error('请输入内容');
|
||||
|
||||
// 演示环境下 默认返回的内容
|
||||
if ($this->isDemo) {
|
||||
$content = <<<EOF
|
||||
演示环境中 默认返回的内容
|
||||
|
||||
我来帮你优化这个标题,让它更有吸引力且更符合电商平台的搜索逻辑:
|
||||
|
||||
"商务男士高端定制马克杯 | 办公室精英必备 | 优质陶瓷防烫手柄"
|
||||
|
||||
这个优化后的标题:
|
||||
1. 突出了目标用户群体(商务男士)
|
||||
2. 强调了产品定位(高端定制)
|
||||
3. 点明了使用场景(办公室)
|
||||
4. 添加了材质和功能特点(优质陶瓷、防烫手柄)
|
||||
5. 使用了吸引人的关键词(精英必备)
|
||||
|
||||
这样的标题不仅更具体,也更容易被搜索引擎识别,同时能精准触达目标客户群。您觉得这个版本如何?
|
||||
EOF;
|
||||
$choices = [['message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => $content,
|
||||
]]];
|
||||
$this->success('success', compact('choices'));
|
||||
}
|
||||
|
||||
try {
|
||||
$result = AiChatService::instance()
|
||||
// 当使用推理模型时,可能存在超时的情况,所以需要设置超时时间为 0
|
||||
// ->setTimeLimit(0)
|
||||
// 请替换为您需要的模型类型
|
||||
->setAiType(AiType::QWEN)
|
||||
// 如果需要指定模型的 API 地址,可自行设置
|
||||
// ->setAiUrl('https://xxx.com')
|
||||
// 请替换为您的模型
|
||||
->setAiModel('qwen-plus')
|
||||
// 请替换为您的 API KEY
|
||||
->setAiKey('sk-1234567890')
|
||||
// 此内容会作为系统提示,会影响到回答的内容 当前仅作为测试使用
|
||||
->setSystemContent('你现在是一位资深的海外电商产品经理')
|
||||
->chat($message);
|
||||
$choices = $result['choices'];
|
||||
}catch (\Throwable $exception) {
|
||||
$choices = [['message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => $exception->getMessage(),
|
||||
]]];
|
||||
}
|
||||
$this->success('success', compact('choices'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class Admin extends AdminController
|
||||
try {
|
||||
$save = $this->model->save($post);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
$this->error('保存失败' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
@@ -88,18 +88,14 @@ class Admin extends AdminController
|
||||
$post['auth_ids'] = implode(',', array_keys($authIds));
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
if (isset($row['password'])) {
|
||||
unset($row['password']);
|
||||
}
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
TriggerService::updateMenu($id);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
$this->error('保存失败' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$row->auth_ids = explode(',', $row->auth_ids ?: '');
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ class Config extends AdminController
|
||||
if ($group == 'upload') {
|
||||
$upload_types = config('admin.upload_types');
|
||||
// 兼容旧版本
|
||||
$this->model->where('name', 'upload_allow_type')->update(['value' => implode(',', array_keys($upload_types))]);
|
||||
$this->model->removeOption()->where('name', 'upload_allow_type')->update(['value' => implode(',', array_keys($upload_types))]);
|
||||
}
|
||||
foreach ($post as $key => $val) {
|
||||
if (in_array($key, $notAddFields)) continue;
|
||||
if ($this->model->where('name', $key)->count()) {
|
||||
$this->model->where('name', $key)->update(['value' => $val,]);
|
||||
if ($this->model->removeOption()->where('name', $key)->count()) {
|
||||
$this->model->removeOption()->where('name', $key)->update(['value' => $val,]);
|
||||
}else {
|
||||
$this->model->create(
|
||||
[
|
||||
@@ -59,7 +59,7 @@ class Config extends AdminController
|
||||
TriggerService::updateMenu();
|
||||
TriggerService::updateSysConfig();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
$this->error('保存失败' . $e->getMessage());
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class Log extends AdminController
|
||||
}
|
||||
[$page, $limit, $where, $excludeFields] = $this->buildTableParams(['month']);
|
||||
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
|
||||
$model = $this->model->setMonth($month)->with('admin')->where($where);
|
||||
$model = $this->model->setSuffix("_$month")->with('admin')->where($where);
|
||||
try {
|
||||
$count = $model->count();
|
||||
$list = $model->page($page, $limit)->order($this->sort)->select();
|
||||
@@ -60,7 +60,8 @@ class Log extends AdminController
|
||||
$this->error('演示环境下不允许操作');
|
||||
}
|
||||
[$page, $limit, $where, $excludeFields] = $this->buildTableParams(['month']);
|
||||
$tableName = $this->model->getName();
|
||||
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
|
||||
$tableName = $this->model->setSuffix("_$month")->getName();
|
||||
$tableName = CommonTool::humpToLine(lcfirst($tableName));
|
||||
$prefix = config('database.connections.mysql.prefix');
|
||||
$dbList = Db::query("show full columns from {$prefix}{$tableName}");
|
||||
@@ -71,15 +72,18 @@ class Log extends AdminController
|
||||
$header[] = [$comment, $vo['Field']];
|
||||
}
|
||||
}
|
||||
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
|
||||
$model = $this->model->setMonth($month)->with('admin')->where($where);
|
||||
$model = $this->model->setSuffix("_$month")->with('admin')->where($where);
|
||||
try {
|
||||
$list = $model
|
||||
->where($where)
|
||||
->limit(100000)
|
||||
->limit(10000)
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($list as &$vo) {
|
||||
$vo['content'] = json_encode($vo['content'], JSON_UNESCAPED_UNICODE);
|
||||
$vo['response'] = json_encode($vo['response'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}catch (PDOException|DbException $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\admin\service\NodeService;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '系统节点管理')]
|
||||
@@ -55,11 +58,11 @@ class Node extends AdminController
|
||||
|
||||
try {
|
||||
if ($force == 1) {
|
||||
$updateNodeList = $model->whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$updateNodeList = $model->removeOption()->whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($updateNodeList as $vo) {
|
||||
isset($formatNodeList[$vo['node']])
|
||||
&& $model->where('id', $vo['id'])->update(
|
||||
&& $model->removeOption()->where('id', $vo['id'])->update(
|
||||
[
|
||||
'title' => $formatNodeList[$vo['node']]['title'],
|
||||
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
|
||||
@@ -67,7 +70,7 @@ class Node extends AdminController
|
||||
);
|
||||
}
|
||||
}
|
||||
$existNodeList = $model->field('node,title,type,is_auth')->select();
|
||||
$existNodeList = $model->removeOption()->field('node,title,type,is_auth')->select();
|
||||
foreach ($nodeList as $key => $vo) {
|
||||
foreach ($existNodeList as $v) {
|
||||
if ($vo['node'] == $v->node) {
|
||||
@@ -76,8 +79,10 @@ class Node extends AdminController
|
||||
}
|
||||
}
|
||||
}
|
||||
$model->saveAll($nodeList);
|
||||
TriggerService::updateNode();
|
||||
if (!empty($nodeList)) {
|
||||
$model->saveAll($nodeList);
|
||||
TriggerService::updateNode();
|
||||
}
|
||||
}catch (\Exception $e) {
|
||||
$this->error('节点更新失败');
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CheckAuth
|
||||
!$check && $this->error('无权限访问');
|
||||
// 判断是否为演示环境
|
||||
if (env('EASYADMIN.IS_DEMO', false) && $request->isPost()) {
|
||||
if (!in_array($currentNode, ['system.log/record', ''])) $this->error('演示环境下不允许修改');
|
||||
if (!in_array($currentNode, ['system.log/record', 'mall.goods/aiOptimization'])) $this->error('演示环境下不允许修改');
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
|
||||
@@ -8,6 +8,11 @@ use app\common\model\TimeModel;
|
||||
class MallCate extends TimeModel
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,12 @@ use think\model\relation\HasOne;
|
||||
|
||||
class MallGoods extends TimeModel
|
||||
{
|
||||
|
||||
protected $table = "";
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
// * +++++++++++++++++++++++++++
|
||||
// | 以下两种写法适用于 with 关联
|
||||
|
||||
@@ -8,7 +8,12 @@ use app\common\model\TimeModel;
|
||||
class SystemAdmin extends TimeModel
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
public array $notes = [
|
||||
'login_type' => [
|
||||
@@ -17,12 +22,15 @@ class SystemAdmin extends TimeModel
|
||||
],
|
||||
];
|
||||
|
||||
public function getAuthList()
|
||||
public function getAuthIdsAttr($value): array
|
||||
{
|
||||
$list = (new SystemAuth())
|
||||
->where('status', 1)
|
||||
->column('title', 'id');
|
||||
return $list;
|
||||
if (!$value) return [];
|
||||
return explode(',', $value);
|
||||
}
|
||||
|
||||
public function getAuthList(): array
|
||||
{
|
||||
return (new SystemAuth())->removeOption()->where('status', 1)->column('title', 'id');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,44 +3,52 @@
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
class SystemAuth extends TimeModel
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色ID获取授权节点
|
||||
* @param $authId
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function getAuthorizeNodeListByAdminId($authId)
|
||||
public function getAuthorizeNodeListByAdminId($authId): array
|
||||
{
|
||||
$checkNodeList = (new SystemAuthNode())
|
||||
->where('auth_id', $authId)
|
||||
->column('node_id');
|
||||
$systemNode = new SystemNode();
|
||||
$nodelList = $systemNode
|
||||
$systemNode = new SystemNode();
|
||||
$nodeList = $systemNode
|
||||
->where('is_auth', 1)
|
||||
->field('id,node,title,type,is_auth')
|
||||
->select()
|
||||
->toArray();
|
||||
$newNodeList = [];
|
||||
foreach ($nodelList as $vo) {
|
||||
$newNodeList = [];
|
||||
foreach ($nodeList as $vo) {
|
||||
if ($vo['type'] == 1) {
|
||||
$vo = array_merge($vo, ['field' => 'node', 'spread' => true]);
|
||||
$vo = array_merge($vo, ['field' => 'node', 'spread' => true]);
|
||||
$vo['checked'] = false;
|
||||
$vo['title'] = "{$vo['title']}【{$vo['node']}】";
|
||||
$children = [];
|
||||
foreach ($nodelList as $v) {
|
||||
$vo['title'] = "{$vo['title']}【{$vo['node']}】";
|
||||
$children = [];
|
||||
foreach ($nodeList as $v) {
|
||||
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
|
||||
$v = array_merge($v, ['field' => 'node', 'spread' => true]);
|
||||
$v = array_merge($v, ['field' => 'node', 'spread' => true]);
|
||||
$v['checked'] = in_array($v['id'], $checkNodeList) ? true : false;
|
||||
$v['title'] = "{$v['title']}【{$v['node']}】";
|
||||
$children[] = $v;
|
||||
$v['title'] = "{$v['title']}【{$v['node']}】";
|
||||
$children[] = $v;
|
||||
}
|
||||
}
|
||||
!empty($children) && $vo['children'] = $children;
|
||||
|
||||
@@ -4,24 +4,23 @@ namespace app\admin\model;
|
||||
|
||||
use app\admin\service\SystemLogService;
|
||||
use app\common\model\TimeModel;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class SystemLog extends TimeModel
|
||||
{
|
||||
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
parent::__construct($data);
|
||||
$this->name = 'system_log_' . date('Ym');
|
||||
}
|
||||
protected array $type = [
|
||||
'content' => 'json',
|
||||
'response' => 'json',
|
||||
];
|
||||
|
||||
public function setMonth($month)
|
||||
protected function init(): void
|
||||
{
|
||||
SystemLogService::instance()->detectTable();
|
||||
$this->name = 'system_log_' . $month;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function admin()
|
||||
|
||||
public function admin(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo('app\admin\model\SystemAdmin', 'admin_id', 'id');
|
||||
}
|
||||
|
||||
@@ -4,31 +4,41 @@ namespace app\admin\model;
|
||||
|
||||
use app\common\constants\MenuConstant;
|
||||
use app\common\model\TimeModel;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
class SystemMenu extends TimeModel
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
public function getPidMenuList()
|
||||
protected function getOptions(): array
|
||||
{
|
||||
$list = $this->field('id,pid,title')
|
||||
->where([
|
||||
['pid', '<>', MenuConstant::HOME_PID],
|
||||
['status', '=', 1],
|
||||
])
|
||||
->select()
|
||||
->toArray();
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DbException
|
||||
* @throws DataNotFoundException
|
||||
*/
|
||||
public function getPidMenuList(): array
|
||||
{
|
||||
$list = $this->removeOption()->field('id,pid,title')->where([
|
||||
['pid', '<>', MenuConstant::HOME_PID],
|
||||
['status', '=', 1],
|
||||
])->select()->toArray();
|
||||
|
||||
$pidMenuList = $this->buildPidMenu(0, $list);
|
||||
$pidMenuList = array_merge([[
|
||||
return array_merge([[
|
||||
'id' => 0,
|
||||
'pid' => 0,
|
||||
'title' => '顶级菜单',
|
||||
]], $pidMenuList);
|
||||
return $pidMenuList;
|
||||
}
|
||||
|
||||
protected function buildPidMenu($pid, $list, $level = 0)
|
||||
protected function buildPidMenu($pid, $list, $level = 0): array
|
||||
{
|
||||
$newList = [];
|
||||
foreach ($list as $vo) {
|
||||
|
||||
@@ -7,22 +7,21 @@ use app\common\model\TimeModel;
|
||||
class SystemNode extends TimeModel
|
||||
{
|
||||
|
||||
public function getNodeTreeList()
|
||||
public function getNodeTreeList(): array
|
||||
{
|
||||
$list = $this->select()->toArray();
|
||||
$list = $this->buildNodeTree($list);
|
||||
return $list;
|
||||
$list = $this->removeOption()->select()->toArray();
|
||||
return $this->buildNodeTree($list);
|
||||
}
|
||||
|
||||
protected function buildNodeTree($list)
|
||||
protected function buildNodeTree($list): array
|
||||
{
|
||||
$newList = [];
|
||||
$newList = [];
|
||||
$repeatString = " ";
|
||||
foreach ($list as $vo) {
|
||||
if ($vo['type'] == 1) {
|
||||
$newList[] = $vo;
|
||||
foreach ($list as $v) {
|
||||
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
|
||||
if ($v['type'] == 2 && str_contains($v['node'], $vo['node'] . '/')) {
|
||||
$v['node'] = "{$repeatString}├{$repeatString}" . $v['node'];
|
||||
$newList[] = $v;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ use app\common\model\TimeModel;
|
||||
class SystemQuick extends TimeModel
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -151,12 +151,18 @@
|
||||
<tr>
|
||||
<td>DEBUG模式</td>
|
||||
<td>
|
||||
<button type="button" class="layui-btn layui-btn-xs {:env('APP_DEBUG')?'layui-btn-warm':'layui-bg-gray'}">
|
||||
<button type="button" class="layui-btn layui-btn-xs {:env('APP_DEBUG')?'layui-bg-cyan':'layui-bg-gray'}">
|
||||
{:env('APP_DEBUG')?'开启中':'已关闭'}
|
||||
</button>
|
||||
<span class="layui-badge layui-bg-gray">建议线上环境关闭 APP_DEBUG</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>composer信息</td>
|
||||
<td>
|
||||
<button type="button" class="layui-btn layui-btn-xs layui-bg-cyan" lay-on="showComposerInfo">点击查看</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>主要特色</td>
|
||||
<td>
|
||||
|
||||
@@ -26,9 +26,18 @@
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">商品标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="">
|
||||
<div class="layui-row">
|
||||
<label class="layui-form-label required">商品标题</label>
|
||||
<div class="layui-input-block layui-col-space5">
|
||||
<div class="layui-col-xs10">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs2">
|
||||
<button class="layui-btn layui-bg-purple layui-btn-fluid" type="button" lay-on="AiOptimization">AI优化</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,12 +26,22 @@
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">商品标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="{$row.title|default=''}">
|
||||
<div class="layui-row">
|
||||
<label class="layui-form-label required">商品标题</label>
|
||||
<div class="layui-input-block layui-col-space5">
|
||||
<div class="layui-col-xs10">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="{$row.title|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs2">
|
||||
<button class="layui-btn layui-bg-purple layui-btn-fluid" type="button" lay-on="AiOptimization">AI优化</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label required">商品LOGO</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main" id="app">
|
||||
|
||||
<div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
|
||||
<ul class="layui-tab-title">
|
||||
<div class="layui-tabs layui-tabs-card layui-panel " id="docDemoTabBrief">
|
||||
<ul class="layui-tabs-header layui-bg-tint">
|
||||
<li class="layui-this" data-group="site">网站设置</li>
|
||||
<li data-group="logo">LOGO配置</li>
|
||||
<li data-group="upload">上传配置</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
{include file="system/config/site" /}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{include file="system/config/logo" /}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{include file="system/config/upload" /}
|
||||
</div>
|
||||
<div class="layui-tabs-body">
|
||||
<div class="layui-tabs-item layui-show"> {include file="system/config/site" /}</div>
|
||||
<div class="layui-tabs-item"> {include file="system/config/logo" /}</div>
|
||||
<div class="layui-tabs-item">{include file="system/config/upload" /}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -100,21 +100,21 @@ if (!function_exists('auth')) {
|
||||
$authService = new AuthService(session('admin.id'));
|
||||
return $authService->checkNode($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $detail
|
||||
* @param string $name
|
||||
* @param string $placeholder
|
||||
* @return string
|
||||
*/
|
||||
function editor_textarea(?string $detail, string $name = 'desc', string $placeholder = '请输入'): string
|
||||
{
|
||||
$editor_type = sysConfig('site', 'editor_type');
|
||||
return match ($editor_type) {
|
||||
'ckeditor' => "<textarea name='{$name}' rows='20' class='layui-textarea editor' placeholder='{$placeholder}'>{$detail}</textarea>",
|
||||
'ueditor' => "<script type='text/plain' id='{$name}' name='{$name}' class='editor' data-content='{$detail}'></script>",
|
||||
'EasyMDE' => "<textarea id='{$name}' class='editor' name='{$name}'>{$detail}</textarea>",
|
||||
default => "<div class='wangEditor_div'><textarea name='{$name}' rows='20' class='layui-textarea editor layui-hide'>{$detail}</textarea><div id='editor_toolbar_{$name}'></div><div id='editor_{$name}' style='height: 300px'></div></div>",
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param string|null $detail
|
||||
* @param string $name
|
||||
* @param string $placeholder
|
||||
* @return string
|
||||
*/
|
||||
function editor_textarea(?string $detail, string $name = 'desc', string $placeholder = '请输入'): string
|
||||
{
|
||||
$editor_type = sysConfig('site', 'editor_type');
|
||||
return match ($editor_type) {
|
||||
'ckeditor' => "<textarea name='{$name}' rows='20' class='layui-textarea editor' placeholder='{$placeholder}'>{$detail}</textarea>",
|
||||
'ueditor' => "<script type='text/plain' id='{$name}' name='{$name}' class='editor' data-content='{$detail}'></script>",
|
||||
'EasyMDE' => "<textarea id='{$name}' class='editor' name='{$name}'>{$detail}</textarea>",
|
||||
default => "<div class='wangEditor_div'><textarea name='{$name}' rows='20' class='layui-textarea editor layui-hide'>{$detail}</textarea><div id='editor_toolbar_{$name}'></div><div id='editor_{$name}' style='height: 300px'></div></div>",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,28 +13,20 @@ use think\model\concern\SoftDelete;
|
||||
class TimeModel extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 自动时间戳类型
|
||||
* @var string
|
||||
*/
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
/**
|
||||
* 添加时间
|
||||
* @var string
|
||||
*/
|
||||
protected $createTime = 'create_time';
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
* @var string
|
||||
*/
|
||||
protected $updateTime = 'update_time';
|
||||
|
||||
/**
|
||||
* 软删除
|
||||
*/
|
||||
use SoftDelete;
|
||||
protected $deleteTime = false;
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'autoWriteTimestamp' => true,
|
||||
'createTime' => 'create_time',
|
||||
'updateTime' => 'update_time',
|
||||
'deleteTime' => false,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
"topthink/think-view": "^2.0",
|
||||
"topthink/think-captcha": "^3.0",
|
||||
"topthink/think-filesystem": "^2.0",
|
||||
"aliyuncs/oss-sdk-php": "^2.6",
|
||||
"aliyuncs/oss-sdk-php": "^2.7.2",
|
||||
"qcloud/cos-sdk-v5": "^2.6",
|
||||
"jianyan74/php-excel": "^1.0.2",
|
||||
"doctrine/annotations": "^2.0.0",
|
||||
@@ -37,6 +37,7 @@
|
||||
"wolf-leo/phplogviewer": "^0.11.3",
|
||||
"wolfcode/authenticator": "^0.0.6",
|
||||
"wolfcode/rate-limiting": "^0.1.0",
|
||||
"wolfcode/php-ai": "^0.1.2",
|
||||
"ext-json": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-pdo": "*"
|
||||
|
||||
@@ -571,3 +571,22 @@ INSERT INTO `ea_system_uploadfile`
|
||||
VALUES ('290', 'oss', 'image/jpeg', 'https://lxn-99php.oss-cn-shenzhen.aliyuncs.com/upload/20191111/2c412adf1b30c8be3a913e603c7b6e4a.jpg', '', '', '', '0', 'image/jpeg', '0', 'jpg', '', 1573612437, null, null);
|
||||
INSERT INTO `ea_system_uploadfile`
|
||||
VALUES ('296', 'cos', 'image/jpeg', 'https://easyadmin-1251997243.cos.ap-guangzhou.myqcloud.com/upload/20191114/2381eaf81208ac188fa994b6f2579953.jpg', '', '', '', '0', 'image/jpeg', '0', 'jpg', '', 1573612437, null, null);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ea_system_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `ea_system_log`;
|
||||
CREATE TABLE `ea_system_log`
|
||||
(
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`admin_id` int unsigned DEFAULT '0' COMMENT '管理员ID',
|
||||
`url` varchar(1500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作页面',
|
||||
`method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '请求方法',
|
||||
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '日志标题',
|
||||
`content` json NOT NULL COMMENT '请求数据',
|
||||
`response` json DEFAULT NULL COMMENT '回调数据',
|
||||
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP',
|
||||
`useragent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'User-Agent',
|
||||
`create_time` int DEFAULT NULL COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='后台操作日志表 - 202412';
|
||||
|
||||
@@ -140,6 +140,38 @@ define(["jquery", "easy-admin", "echarts", "echarts-theme", "miniAdmin", "miniTh
|
||||
echartsRecords.resize();
|
||||
});
|
||||
})
|
||||
|
||||
let util = layui.util;
|
||||
util.on({
|
||||
showComposerInfo: function () {
|
||||
// <div style="padding: 25px;">12313</div>
|
||||
let html = ``
|
||||
ea.request.post({
|
||||
url: ea.url('ajax/composerInfo'),
|
||||
}, function (success) {
|
||||
let data = success.data
|
||||
data.forEach(function (item) {
|
||||
html += `${item.name} ${item.version}\r\n`
|
||||
})
|
||||
html = `<pre class="layui-code code-demo">${html}</pre>`
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: 'composer 信息',
|
||||
area: ['50%', '90%'],
|
||||
shade: 0.8,
|
||||
shadeClose: true,
|
||||
content: html,
|
||||
success: function () {
|
||||
layui.code({elem: '.code-demo', theme: 'dark', lang: 'php'});
|
||||
}
|
||||
})
|
||||
}, function (error) {
|
||||
console.error(error)
|
||||
return false;
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
editAdmin: function () {
|
||||
let form = layui.form
|
||||
|
||||
@@ -85,13 +85,55 @@ define(["jquery", "easy-admin"], function ($, ea) {
|
||||
ea.listen();
|
||||
},
|
||||
add: function () {
|
||||
layui.util.on({
|
||||
AiOptimization: function (data) {
|
||||
let layOn = $(data).attr('lay-on')
|
||||
$(data).attr('lay-on', layOn + 'Loading')
|
||||
aiOptimization(data)
|
||||
},
|
||||
})
|
||||
ea.listen();
|
||||
},
|
||||
edit: function () {
|
||||
layui.util.on({
|
||||
AiOptimization: function (data) {
|
||||
let layOn = $(data).attr('lay-on')
|
||||
$(data).attr('lay-on', layOn + 'Loading')
|
||||
aiOptimization(data)
|
||||
},
|
||||
})
|
||||
ea.listen();
|
||||
},
|
||||
stock: function () {
|
||||
ea.listen();
|
||||
},
|
||||
};
|
||||
|
||||
function aiOptimization(data) {
|
||||
let layOn = $(data).attr('lay-on')
|
||||
let title = $('input[name="title"]').val()
|
||||
|
||||
// 告诉AI 你需要做什么
|
||||
let message = `优化这个标题 ${title}`
|
||||
|
||||
if ($.trim(title) === '') {
|
||||
ea.msg.error('标题不能为空', function () {
|
||||
$(data).attr('lay-on', layOn.split('Loading')[0])
|
||||
})
|
||||
return false
|
||||
}
|
||||
let url = ea.url('mall.goods/aiOptimization')
|
||||
ea.request.post({url: url, data: {message: message}}, function (res) {
|
||||
let content = res.data?.choices[0]?.message?.content
|
||||
// stream 为true 时,AI 内容会逐字输出
|
||||
let stream = true
|
||||
ea.ai.chat(content, {stream: stream}, function () {
|
||||
$(data).attr('lay-on', layOn.split('Loading')[0])
|
||||
})
|
||||
}, function (error) {
|
||||
ea.msg.error(error.msg, function () {
|
||||
$(data).attr('lay-on', layOn.split('Loading')[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
@@ -1,15 +1,15 @@
|
||||
define(["jquery", "easy-admin", "vue"], function ($, ea, Vue) {
|
||||
define(["jquery", "easy-admin"], function ($, ea) {
|
||||
|
||||
var form = layui.form;
|
||||
|
||||
return {
|
||||
index: function () {
|
||||
var _group = 'site'
|
||||
var element = layui.element;
|
||||
element.on('tab(docDemoTabBrief)', function (data) {
|
||||
let tabs = layui.tabs
|
||||
var TABS_ID = 'docDemoTabBrief';
|
||||
tabs.on(`afterChange(${TABS_ID})`, function (data) {
|
||||
_group = $(this).data('group')
|
||||
});
|
||||
|
||||
})
|
||||
let _upload_type = upload_type || 'local'
|
||||
$('.upload_type').addClass('layui-hide')
|
||||
$('.' + _upload_type).removeClass('layui-hide')
|
||||
@@ -20,12 +20,15 @@ define(["jquery", "easy-admin", "vue"], function ($, ea, Vue) {
|
||||
$('.' + _upload_type).removeClass('layui-hide')
|
||||
});
|
||||
|
||||
|
||||
form.on("submit", function (data) {
|
||||
data.field['group'] = _group
|
||||
});
|
||||
|
||||
ea.listen();
|
||||
ea.listen('', function (res) {
|
||||
ea.msg.success(res.msg);
|
||||
}, function (err) {
|
||||
ea.msg.error(err.msg);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -51,7 +51,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
|
||||
field: 'content', minWidth: 200, title: '请求数据', align: "left", templet: function (res) {
|
||||
let html = '<div class="layui-colla-item">' +
|
||||
'<div class="layui-colla-title">点击预览</div>' +
|
||||
'<div class="layui-colla-content">' + prettyFormat(res.content) + '</div>' +
|
||||
'<div class="layui-colla-content">' + prettyFormat(JSON.stringify(res.content)) + '</div>' +
|
||||
'</div>'
|
||||
return '<div class="layui-collapse" lay-accordion>' + html + '</div>'
|
||||
}
|
||||
@@ -60,7 +60,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
|
||||
field: 'response', minWidth: 200, title: '回调数据', align: "left", templet: function (res) {
|
||||
let html = '<div class="layui-colla-item">' +
|
||||
'<div class="layui-colla-title">点击预览</div>' +
|
||||
'<div class="layui-colla-content">' + prettyFormat(res.response) + '</div>' +
|
||||
'<div class="layui-colla-content">' + prettyFormat(JSON.stringify(res.response)) + '</div>' +
|
||||
'</div>'
|
||||
return '<div class="layui-collapse" lay-accordion>' + html + '</div>'
|
||||
}
|
||||
|
||||
BIN
public/static/common/images/loading.gif
Normal file
BIN
public/static/common/images/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
@@ -22,6 +22,7 @@ require.config({
|
||||
"vue": ["plugs/vue-2.6.10/vue.min"],
|
||||
"swiper": ["plugs/swiper/swiper-bundle.min"],
|
||||
"colorMode": ["plugs/colorMode/colorMode"],
|
||||
"lazyload": ["plugs/lazyload/lazyload.min"],
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSelect, miniTheme, xmSelect) {
|
||||
define(["jquery", "tableSelect", "miniTheme", "xmSelect", "lazyload"], function ($, tableSelect, miniTheme, xmSelect, lazyload) {
|
||||
|
||||
//切换日夜模式
|
||||
window.onInitElemStyle = function () {
|
||||
@@ -748,8 +748,11 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
|
||||
var values = value.split(option.imageSplit),
|
||||
valuesHtml = [];
|
||||
values.forEach((value, index) => {
|
||||
valuesHtml.push('<img style="max-width: ' + option.imageWidth + 'px; max-height: ' + option.imageHeight + 'px;" src="' + value + '" data-image="' + title + '">');
|
||||
valuesHtml.push('<img style="max-width: ' + option.imageWidth + 'px; max-height: ' + option.imageHeight + 'px;" class="lazyload" src="/static/common/images/loading.gif" data-src="' + value + '" data-image="' + title + '">');
|
||||
});
|
||||
$(function () {
|
||||
$("img.lazyload").lazyload({threshold: 1});
|
||||
})
|
||||
return valuesHtml.join(option.imageJoin);
|
||||
}
|
||||
},
|
||||
@@ -1593,7 +1596,7 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
|
||||
cols: [[
|
||||
{type: selectCheck},
|
||||
{field: 'id', title: 'ID'},
|
||||
{field: 'url', minWidth: 80, search: false, title: '图片信息', imageHeight: 40, align: "center", templet: admin.table.image},
|
||||
{field: 'url', minWidth: 80, search: false, title: '图片信息', imageHeight: 30, align: "center", templet: admin.table.image},
|
||||
{field: 'original_name', width: 150, title: '文件原名', align: "center"},
|
||||
{field: 'mime_type', width: 120, title: 'mime类型', align: "center"},
|
||||
{field: 'create_time', width: 200, title: '创建时间', align: "center", search: 'range'},
|
||||
@@ -1761,6 +1764,61 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
|
||||
}
|
||||
},
|
||||
},
|
||||
ai: {
|
||||
chat: function (content, options, cancel) {
|
||||
let id = 'chat_' + (new Date()).getTime()
|
||||
layer.open({
|
||||
'title': options?.title || 'AI建议',
|
||||
type: 1,
|
||||
area: options?.area || ['42%', '60%'],
|
||||
shade: options?.shade || 0,
|
||||
shadeClose: options?.shadeClose || false,
|
||||
scrollbar: options?.scrollbar || false,
|
||||
maxmin: options?.maxmin || true,
|
||||
anim: options?.anim || 0,
|
||||
content: `<div style="padding: 20px;white-space: pre-wrap;" id="${id}"></div>`,
|
||||
success: function (layero, index) {
|
||||
let elem = document.getElementById(id)
|
||||
if (options?.stream) {
|
||||
clearTimeout(aiStreamTimeout)
|
||||
aiStreamCurrentIndex = 0
|
||||
setTimeout(() => {
|
||||
admin.ai.streamOutput(elem, content)
|
||||
}, 300)
|
||||
} else {
|
||||
content = content.replace(/\r\n/g, '<br>').replace(/\n/g, '<br>')
|
||||
setTimeout(() => {
|
||||
elem.innerHTML = content
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
cancel: function (index, layero) {
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
},
|
||||
streamOutput: function (dom, htmlContent) {
|
||||
const chunkSize = 1;
|
||||
let length = htmlContent.length;
|
||||
if (aiStreamCurrentIndex < length) {
|
||||
const endIndex = Math.min(aiStreamCurrentIndex + chunkSize, length);
|
||||
const chunk = htmlContent.slice(aiStreamCurrentIndex, endIndex);
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = chunk;
|
||||
while (tempDiv.firstChild) {
|
||||
dom.appendChild(tempDiv.firstChild);
|
||||
}
|
||||
aiStreamCurrentIndex = endIndex;
|
||||
aiStreamTimeout = setTimeout(() => {
|
||||
admin.ai.streamOutput(dom, htmlContent);
|
||||
dom.scrollIntoView({behavior: "smooth", block: "end"});
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
var aiStreamCurrentIndex = 0;
|
||||
var aiStreamTimeout = null;
|
||||
|
||||
return admin;
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/static/plugs/lazyload/lazyload.min.js
vendored
Normal file
2
public/static/plugs/lazyload/lazyload.min.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/*! Lazy Load 2.0.0-rc.2 - MIT license - Copyright 2007-2019 Mika Tuupola */
|
||||
!function(t,e){"object"==typeof exports?module.exports=e(t):"function"==typeof define&&define.amd?define([],e):t.LazyLoad=e(t)}("undefined"!=typeof global?global:this.window||this.global,function(t){"use strict";function e(t,e){this.settings=s(r,e||{}),this.images=t||document.querySelectorAll(this.settings.selector),this.observer=null,this.init()}"function"==typeof define&&define.amd&&(t=window);const r={src:"data-src",srcset:"data-srcset",selector:".lazyload",root:null,rootMargin:"0px",threshold:0},s=function(){let t={},e=!1,r=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],r++);for(;r<o;r++)!function(r){for(let o in r)Object.prototype.hasOwnProperty.call(r,o)&&(e&&"[object Object]"===Object.prototype.toString.call(r[o])?t[o]=s(!0,t[o],r[o]):t[o]=r[o])}(arguments[r]);return t};if(e.prototype={init:function(){if(!t.IntersectionObserver)return void this.loadImages();let e=this,r={root:this.settings.root,rootMargin:this.settings.rootMargin,threshold:[this.settings.threshold]};this.observer=new IntersectionObserver(function(t){Array.prototype.forEach.call(t,function(t){if(t.isIntersecting){e.observer.unobserve(t.target);let r=t.target.getAttribute(e.settings.src),s=t.target.getAttribute(e.settings.srcset);"img"===t.target.tagName.toLowerCase()?(r&&(t.target.src=r),s&&(t.target.srcset=s)):t.target.style.backgroundImage="url("+r+")"}})},r),Array.prototype.forEach.call(this.images,function(t){e.observer.observe(t)})},loadAndDestroy:function(){this.settings&&(this.loadImages(),this.destroy())},loadImages:function(){if(!this.settings)return;let t=this;Array.prototype.forEach.call(this.images,function(e){let r=e.getAttribute(t.settings.src),s=e.getAttribute(t.settings.srcset);"img"===e.tagName.toLowerCase()?(r&&(e.src=r),s&&(e.srcset=s)):e.style.backgroundImage="url('"+r+"')"})},destroy:function(){this.settings&&(this.observer.disconnect(),this.settings=null)}},t.lazyload=function(t,r){return new e(t,r)},t.jQuery){const r=t.jQuery;r.fn.lazyload=function(t){return t=t||{},t.attribute=t.attribute||"data-src",new e(r.makeArray(this),t),this}}return e});
|
||||
Reference in New Issue
Block a user