更新666

This commit is contained in:
Alone
2024-11-04 11:35:32 +08:00
parent 16fd7106d6
commit 3777395105
17 changed files with 976 additions and 117 deletions
+10
View File
@@ -4,6 +4,16 @@
## 更新日志
### v2.1
- 增加网页全网搜功能
- 对接微信对话功能
- 更新优化Seo
- 后台增加批量删除功能
- 优化表格导入功能
注:如何更新见文件夹updateLog/24.11.4
### v2
- UI改版:不再使用uniapp
+19 -14
View File
@@ -153,14 +153,18 @@ class Source extends QfShop
if (!$this->pk_value) {
return jerr($this->pk . "必须填写", 400);
}
//根据主键获取一行数据
$item = $this->getRowByPk();
if (empty($item)) {
return jerr("数据查询失败", 404);
}
$this->model->where($this->pk, $this->pk_value)->delete();
if (isInteger($this->pk_value)){
//根据主键获取一行数据
$item = $this->getRowByPk();
if (empty($item)) {
return jerr("数据查询失败", 404);
}
$this->model->where($this->pk, $this->pk_value)->delete();
}else{
$list = explode(',', $this->pk_value);
$this->model->where($this->pk, 'in', $list)->delete();
}
return jok('删除成功');
}
@@ -192,7 +196,7 @@ class Source extends QfShop
if ($error) {
return $error;
}
// try {
try {
$file = request()->file('file');
try {
validate(['file' => 'filesize:' . config("qfshop.upload_max_file") . '|fileExt:' . config("qfshop.upload_file_type")])
@@ -205,11 +209,12 @@ class Source extends QfShop
$extension = pathinfo($file_name, PATHINFO_EXTENSION);
if ($extension == 'csv') {
return jerr('转成xlsx格式吧');
$PHPReader = new \PHPExcel_Reader_CSV();
$encoding = $this->detectFileEncoding($file_name);
if (!$encoding || strtoupper($encoding) == 'UTF-8') {
$encoding = 'GBK'; // 尝试将其强制转为GBK
}
// if (!$encoding || strtoupper($encoding) == 'UTF-8') {
// $encoding = 'GBK'; // 尝试将其强制转为GBK
// }
$PHPReader->setInputEncoding($encoding);
$PHPReader->setDelimiter(',');
} elseif ($extension == 'xlsx') {
@@ -284,9 +289,9 @@ class Source extends QfShop
} catch (ValidateException $e) {
return jerr($e->getMessage());
}
// } catch (\Exception $error) {
// return jerr('上传文件失败,请检查你的文件!');
// }
} catch (\Exception $error) {
return jerr('上传文件失败,请检查你的文件!');
}
}
+31 -18
View File
@@ -22,11 +22,11 @@ class SourceCategory extends QfShop
];
$this->insertFields = [
//允许添加的字段列表
"name","sort","status","image"
"name","sort","status","image","is_update"
];
$this->updateFields = [
//允许更新的字段列表
"name","sort","status","image"
"name","sort","status","image","is_update"
];
$this->insertRequire = [
//添加时必须填写的字段
@@ -196,6 +196,19 @@ class SourceCategory extends QfShop
if (!$source_category_id) {
return jerr("ID参数必须填写", 400);
}
$d = [
"status" => 1,
"update_time" => time(),
];
if(input("type") == 1){
$d = [
"is_update" => 0,
"update_time" => time(),
];
}
if (isInteger($source_category_id)) {
//根据主键获取一行数据
$item = $this->model->where("source_category_id", $source_category_id)->field($this->selectDetail)->find();
@@ -204,17 +217,11 @@ class SourceCategory extends QfShop
}
//单个操作
$map = ["source_category_id" => $source_category_id];
$this->model->where($map)->update([
"status" => 1,
"update_time" => time(),
]);
$this->model->where($map)->update($d);
} else {
//批量操作
$list = explode(',', $source_category_id);
$this->model->where("source_category_id", 'in', $list)->update([
"status" => 1,
"update_time" => time(),
]);
$this->model->where("source_category_id", 'in', $list)->update($d);
}
return jok("禁用成功");
}
@@ -236,6 +243,18 @@ class SourceCategory extends QfShop
if (!$source_category_id) {
return jerr("ID参数必须填写", 400);
}
$d = [
"status" => 0,
"update_time" => time(),
];
if(input("type") == 1){
$d = [
"is_update" => 1,
"update_time" => time(),
];
}
if (isInteger($source_category_id)) {
//根据主键获取一行数据
$item = $this->model->where("source_category_id", $source_category_id)->field($this->selectDetail)->find();
@@ -244,17 +263,11 @@ class SourceCategory extends QfShop
}
//单个操作
$map = ["source_category_id" => $source_category_id];
$this->model->where($map)->update([
"status" => 0,
"update_time" => time(),
]);
$this->model->where($map)->update($d);
} else {
//批量操作
$list = explode(',', $source_category_id);
$this->model->where("source_category_id", 'in', $list)->update([
"status" => 0,
"update_time" => time(),
]);
$this->model->where("source_category_id", 'in', $list)->update($d);
}
return jok("启用成功");
}
+382
View File
@@ -0,0 +1,382 @@
<?php
namespace app\api\controller;
use app\api\QfShop;
use think\facade\Request;
use think\facade\Cache;
use app\model\Source as SourceModel;
class Chatbot extends QfShop
{
// 应用管理 开放API 生成三个参数填在下方 回调地址:https://XXXX/api/chatbot 填在微信官方 即可
private $appId = '';
private $token = '';
private $encodingAESKey = '';
// 回复内容在下方,如有需要自行修改
private $wName = '心悦搜索';
private $key;
public function __construct()
{
$this->key = base64_decode($this->encodingAESKey . "=");
}
public function index()
{
$encrypted = input('encrypted');
//解决全局搜时会多次接收相同消息导致多次请求的bug,原因未知
if(!empty($encrypted)){
if(Cache::get($encrypted) == 1){
return;
}
Cache::set($encrypted, 1, 600);
}
$msg = $this->decryptMessage($encrypted);
$msgtype = $msg['content']['msgtype']??'';
$message = $msg['content']['msg']??'';
if ($msgtype !== 'text' || empty($message)) {
if(Cache::get($msg['userid']) != 1){
$messages = "欢迎来到 ".$this->wName."!🍿🎬 这里是影迷的梦幻天堂,准备好享受每一部影视的精彩时刻吧!\n\n";
$messages .= "🔍 使用指南 🔍\n\n";
$messages .= "1. 搜剧命令\n";
$messages .= "回复 “搜剧+剧名”,免费获取最全的影视资源。\n";
$messages .= "示例:<a href='weixin://bizmsgmenu?msgmenucontent=搜剧我被美女包围了&msgmenuid=搜剧我被美女包围了'>搜剧我被美女包围了</a>\n\n";
$messages .= "2. 全网搜\n";
$messages .= "回复 “全网搜+关键词”,快速找到全网资源!\n";
$messages .= "示例:<a href='weixin://bizmsgmenu?msgmenucontent=全网搜学剪辑&msgmenuid=全网搜学剪辑'>全网搜学剪辑</a>\n\n";
$messages .= "赶快准备好你的爆米花,和我们一起开启下一场视觉盛宴吧!🎥";
$this->sendMessage($msg, $messages);
}
Cache::set($msg['userid'], 1, 604800);
}
// 检查用户消息内容中开头第一个字是否包含“搜”关键字
if (strncmp($message, '搜', 1) === 0 || strncmp($message, '全网搜', 1) === 0) {
if(strncmp($message, '全网搜', 1) === 0){
$list = [];
// 去除“全网搜”关键字,提取用户输入的剧名
$newString = preg_replace('/^全网搜/u', '', $message);
}else{
// 去除“搜剧”关键字,提取用户输入的剧名
$newString = preg_replace('/^搜剧|^搜/u', '', $message);
// 实例化资源模型,并搜索匹配的剧名
$map['page_size'] = 5;
$map['title'] = $newString;
$SourceModel = new SourceModel();
$result = $SourceModel->getList($map);
$list = $result['items']??[];
}
if(empty($list)){
//系统没有资源去全网搜
$this->sendMessage($msg, "正在深入搜索,请稍等...");
$list = $this->Qsearch($newString);
}
$content = "🔍 ".$newString."丨搜索结果";
$is_times = 0;
if (!empty($list)) {
foreach ($list as $item) {
$content = $content . "\n";
if(!empty($item['is_time']) && $item['is_time'] == 1){
$content = $content . "\n🌐️ " . $item['title'] . "\n<a href='" . $item['url'] . "'>" . $item['url'] . "</a>";
$is_times++;
}else{
$content = $content . "\n" . $item['title'] . "\n<a href='" . $item['url'] . "'>" . $item['url'] . "</a>";
}
}
$content = $content . "\n";
if($is_times>0){
$content = $content . "\n🌐️ 资源来源网络,30分钟后删除";
}else if(!empty($newString)){
$content = $content . "\n 不是短剧?请尝试:<a href='weixin://bizmsgmenu?msgmenucontent=全网搜".$newString."&msgmenuid=全网搜".$newString."'>全网搜".$newString."</a>";
}
$content = $content . "\n ------------------------------------------------";
$content = $content . "\n 欢迎观看!如果喜欢可以喊你的朋友一起来哦";
} else {
// 如果没有找到匹配的剧名,提示用户减少关键词尝试搜索
$content = $content . "\n";
$content = $content . "\n 未找到,可换个关键词尝试哦~";
$content = $content . "\n ⚠️宁少写,不多写、错写~";
}
$this->sendMessage($msg, $content);
}
}
private function Qsearch($newString)
{
$list = [];
$urlData = array(
'title' => $newString,
);
$res = curlHelper(Request::domain()."/api/other/all_search", "POST", $urlData)['body'];
$res = json_decode($res, true);
if($res['code'] === 200){
$list = $res['data']??[];
}
return $list;
}
// 解密消息
private function decryptMessage($encrypted)
{
$result = $this->decrypt($encrypted,$this->appId);
if ($result[0] !== 0) {
return [];
}
return json_decode(json_encode(simplexml_load_string($result[1])), true);
}
// 发送消息
private function sendMessage($msg, $message)
{
$data = $this->createXmlMessage($msg, $message);
$result = $this->encrypt($data,$this->appId);
if ($result[0] !== 0) {
return;
}
$this->sendPostRequest("https://chatbot.weixin.qq.com/openapi/sendmsg/".$this->token, [
'encrypt' => $result[1],
]);
}
// 创建 XML 消息
private function createXmlMessage($msg, $message)
{
return '<xml>
<appid><![CDATA['.$msg['appid'].']]></appid>
<openid><![CDATA['.$msg['userid'].']]></openid>
<msg><![CDATA['.$message.']]></msg>
<channel>'.$msg['channel'].'</channel>
</xml>';
}
private function sendPostRequest($url, $data)
{
$options = [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($data),
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if (curl_errno($ch)) {
// 处理错误(可添加日志)
return ['error' => curl_error($ch)];
}
curl_close($ch);
return json_decode($response, true);
}
/**
* 对密文进行解密
* @param string $encrypted 需要解密的密文
* @return string 解密得到的明文
*/
private function decrypt($encrypted,$appid)
{
try {
if(version_compare(PHP_VERSION, '7','>=')) {
$iv = substr($this->key, 0, 16);
$decrypted = openssl_decrypt($encrypted,'AES-256-CBC',substr($this->key, 0, 32),OPENSSL_ZERO_PADDING,$iv);
} else {
//使用BASE64对需要解密的字符串进行解码
$ciphertext_dec = base64_decode($encrypted);
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = substr($this->key, 0, 16);
mcrypt_generic_init($module, $this->key, $iv);
//解密
$decrypted = mdecrypt_generic($module, $ciphertext_dec);
mcrypt_generic_deinit($module);
mcrypt_module_close($module);
}
} catch (Exception $e) {
return [-40007, null];
}
try {
//去除补位字符
$result = $this->decode($decrypted);
//去除16位随机字符串,网络字节序和AppId
if (strlen($result) < 16)
return "";
$content = substr($result, 16, strlen($result));
$len_list = unpack("N", substr($content, 0, 4));
$xml_len = $len_list[1];
$xml_content = substr($content, 4, $xml_len);
$from_appid = substr($content, $xml_len + 4);
if(version_compare(PHP_VERSION, '7','>=')) {
if (!$appid) {
//如果传入的appid是空的,则认为是订阅号,使用数据中提取出来的appid
$appid = $from_appid;
}
}
} catch (Exception $e) {
//print $e;
return [-40008, null];
}
if ($from_appid != $appid)
return [-40005, null];
//不注释上边两行,避免传入appid是错误的情况
if(version_compare(PHP_VERSION, '7','>=')) {
//增加appid,为了解决后面加密回复消息的时候没有appid的订阅号会无法回复
return array(0, $xml_content, $from_appid);
} else {
return array(0, $xml_content);
}
return $encrypted;
}
/**
* 对解密后的明文进行补位删除
* @param decrypted 解密后的明文
* @return 删除填充补位后的明文
*/
private function decode($text)
{
$pad = ord(substr($text, -1));
if ($pad < 1 || $pad > 32) {
$pad = 0;
}
return substr($text, 0, (strlen($text) - $pad));
}
/**
* 对明文进行加密
* @param string $text 需要加密的明文
* @return string 加密后的密文
*/
private function encrypt($text, $appid)
{
try {
if(version_compare(PHP_VERSION, '7','>=')) {
//获得16位随机字符串,填充到明文之前
$random = $this->getRandomStr();
$text = $random . pack("N", strlen($text)) . $text . $appid;
$iv = substr($this->key, 0, 16);
$text = $this->encode($text);
$encrypted = openssl_encrypt($text,'AES-256-CBC',substr($this->key, 0, 32),OPENSSL_ZERO_PADDING,$iv);
} else {
//获得16位随机字符串,填充到明文之前
$random = $this->getRandomStr();
$text = $random . pack("N", strlen($text)) . $text . $appid;
// 网络字节序
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = substr($this->key, 0, 16);
//使用自定义的填充方式对明文进行补位填充
$text = $this->encode($text);
mcrypt_generic_init($module, $this->key, $iv);
//加密
$encrypted = mcrypt_generic($module, $text);
mcrypt_generic_deinit($module);
mcrypt_module_close($module);
$encrypted = base64_encode($encrypted);
}
//print($encrypted);
//使用BASE64对加密后的字符串进行编码
return [0, $encrypted];
} catch (Exception $e) {
//print $e;
return [-40006, null];
}
}
/**
* 对需要加密的明文进行填充补位
* @param $text 需要进行填充补位操作的明文
* @return 补齐明文字符串
*/
private function encode($text)
{
$block_size = 32;
$text_length = strlen($text);
//计算需要填充的位数
$amount_to_pad = 32 - ($text_length % 32);
if ($amount_to_pad == 0) {
$amount_to_pad = 32;
}
//获得补位所用的字符
$pad_chr = chr($amount_to_pad);
$tmp = "";
for ($index = 0; $index < $amount_to_pad; $index++) {
$tmp .= $pad_chr;
}
return $text . $tmp;
}
/**
* 随机生成16位字符串
* @return string 生成的字符串
*/
private function getRandomStr()
{
$str = "";
$str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
$max = strlen($str_pol) - 1;
for ($i = 0; $i < 16; $i++) {
$str .= $str_pol[mt_rand(0, $max)];
}
return $str;
}
private function writeLog($value)
{
// 获取当前目录
$currentDir = __DIR__; // 当前控制器目录
$logFile = $currentDir . '/log.txt'; // 日志文件名称,可以自定义
// 生成时间戳
$timestamp = date('Y-m-d H:i:s');
// 判断 $value 是否为数组,如果是,则转换为 JSON 字符串
if (is_array($value)) {
$value = json_encode($value); // 转换为 JSON 字符串
}
$logEntry = "[$timestamp] 参数值: $value\n"; // 日志内容
// 写入日志文件
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
}
+33 -2
View File
@@ -3,6 +3,7 @@
namespace app\api\controller;
use think\App;
use think\facade\Cache;
use think\facade\Request;
use app\api\QfShop;
use app\model\Source as SourceModel;
@@ -36,7 +37,7 @@ class Other extends QfShop
$map[] = ['is_time', '=', 1];
$map[] = ['title|description', 'like', '%' . trim($title) . '%'];
$urls = $this->model->where($map)->field('source_id as id, title, url')->order('update_time', 'desc')->limit(5)->select()->toArray();
$urls = $this->model->where($map)->field('source_id as id, title, url,is_time')->order('update_time', 'desc')->limit(5)->select()->toArray();
if (!empty($urls)) {
// 获取所有需要更新的ID
@@ -52,6 +53,33 @@ class Other extends QfShop
return jok('临时资源获取成功',$urls);
}
//同一个搜索内容锁机
if (Cache::has($title)) {
// 检查缓存中是否已有结果
return jok('临时资源获取成功1', Cache::get($title));
}
// 检查是否有正在处理的请求
if (Cache::has($title . '_processing')) {
// 如果当前正在处理相同关键词的请求,等待结果
$startTime = time(); // 记录开始时间
while (Cache::has($title . '_processing')) {
usleep(1000000); // 暂停1秒
// 检查是否超过60秒
if (time() - $startTime > 60) {
return jok('临时资源获取成功3', []); // 返回空数组
}
}
return jok('临时资源获取成功2', Cache::get($title));
}
// 设置处理状态为正在处理
Cache::set($title . '_processing', true, 60); // 锁定60秒
$searchList = []; //查询的结果集
$datas = []; //最终数据
@@ -115,6 +143,9 @@ class Other extends QfShop
}
}
}
Cache::set($title, $datas, 60); // 缓存结果60秒
Cache::delete($title . '_processing'); // 解锁
return jok('临时资源获取成功',$datas);
}
@@ -176,7 +207,7 @@ class Other extends QfShop
$data["is_time"] = 1;
$data["update_time"] = time();
$data["create_time"] = time();
$this->model->insertGetId($data);
$data["id"] = $this->model->insertGetId($data);
$datas[] =$data;
$num_success++;
}
+35
View File
@@ -3,6 +3,8 @@
namespace app\api\controller;
use think\App;
use think\facade\Request;
use think\facade\Cache;
use app\api\QfShop;
use app\model\User as Usermodel;
use app\model\Ads as Adsmodel;
@@ -147,4 +149,37 @@ class Tool extends QfShop
}
/**
* 网页端全网搜接口
*
* @return void
*/
public function Qsearch()
{
$title = input('title');
$list = [];
if (empty($title)) {
return jok('临时资源获取成功', $list);
}
$keys = Request::ip()."_".$title;
if(Cache::get($keys) == 1){
return jerr('调用太过频繁啦');
}
Cache::set($keys, 1, 10);
$urlData = array(
'title' => $title,
);
$res = curlHelper(Request::domain()."/api/other/all_search", "POST", $urlData)['body'];
$res = json_decode($res, true);
if($res['code'] === 200){
$list = $res['data']??[];
}
Cache::delete($keys);
return jok('临时资源获取成功', $list);
}
}
+79
View File
@@ -10,6 +10,8 @@ use app\index\QfShop;
use app\model\Source as SourceModel;
use app\model\SourceCategory as SourceCategoryModel;
use Lizhichao\Word\VicWord;
class Index extends QfShop
{
@@ -48,9 +50,26 @@ class Index extends QfShop
});
}
//热门排行榜数据
$hotList = [];
$cacheDir = root_path('runtime/api/cache'); // runtime/cache 目录
foreach ($rankList as $value) {
$cacheFile = $cacheDir . "ranking_data_{$value['name']}.cache";
if (file_exists($cacheFile)) {
$hotList[] = array(
'name'=> $value['name'],
'image'=> $value['image'],
'list'=> json_decode(file_get_contents($cacheFile), true),
);
}
}
$config = config("qfshop");
View::assign('newList', $newList);
View::assign('hotList', $hotList);
View::assign('config', $config);
View::assign('rankList', $rankList);
View::assign('fixed', 1);
@@ -80,9 +99,25 @@ class Index extends QfShop
$category = $this->SourceCategoryModel->field('name,source_category_id as id')->where([['status','=',0]])->order('sort desc')->select();
//热门排行榜数据
$hotList = [];
$cacheDir = root_path('runtime/api/cache'); // runtime/cache 目录
foreach ($rankList as $value) {
$cacheFile = $cacheDir . "ranking_data_{$value['name']}.cache";
if (file_exists($cacheFile)) {
$hotList[] = array(
'name'=> $value['name'],
'image'=> $value['image'],
'list'=> json_decode(file_get_contents($cacheFile), true),
);
}
}
$config = config("qfshop");
View::assign('hotList', $hotList);
View::assign('rankList', $rankList);
View::assign('category', $category);
View::assign('list', $list);
@@ -118,9 +153,53 @@ class Index extends QfShop
}
$rankList = $this->SourceCategoryModel->field('name,image')->where([['status','=',0],['is_sys','=',1]])->order('sort desc')->select();
//热门排行榜数据
$hotList = [];
$cacheDir = root_path('runtime/api/cache'); // runtime/cache 目录
foreach ($rankList as $value) {
$cacheFile = $cacheDir . "ranking_data_{$value['name']}.cache";
if (file_exists($cacheFile)) {
$hotList[] = array(
'name'=> $value['name'],
'image'=> $value['image'],
'list'=> json_decode(file_get_contents($cacheFile), true),
);
}
}
//相关资源
$map[] = ['status', '=', 1];
$map[] = ['is_delete', '=', 0];
$fc = new VicWord();
$keywords = $fc->getAutoWord(preg_replace('/[\\][^\]*[\\]/u', '', $detail['title']));
$keywords = filterAndExtractWords($keywords);
$keywords[] = '';//这个是为了在没有相关资源时不至于获取不到资源
$weightExpr = [];
foreach ($keywords as $keyword) {
$weightExpr[] = "IF(title LIKE '%{$keyword}%' OR description LIKE '%{$keyword}%', 1, 0)";
$searchTitle[] = $keyword;
}
$weightExpr = implode(' + ', $weightExpr);
// 在查询中添加权重计算和排序
$query = $this->SourceModel->alias('a')
->field('a.*, (' . $weightExpr . ') as weight')->where($map)
->where(function($query) use ($keywords) {
foreach ($keywords as $keyword) {
$query->whereOr('title', 'like', '%' . trim($keyword) . '%')
->whereOr('description', 'like', '%' . trim($keyword) . '%');
}
});
$order = ['weight' => 'desc', 'source_id' => 'desc'];
$sameList = $query->where('source_id','<>',$detail['id'])->order($order)->limit(10)->select();
$config = config("qfshop");
View::assign('sameList', $sameList);
View::assign('hotList', $hotList);
View::assign('rankList', $rankList);
View::assign('detail', $detail);
View::assign('config', $config);
+5 -1
View File
@@ -19,6 +19,10 @@
const rankDj = ref([]);
const is_m = ref(0);
const newList = ref([]);
const QStatus = ref(0);
const QList = ref([]);
const QLoading = ref(false);
const total_result = ref(0);
// 公共消息方法
@@ -167,7 +171,7 @@
});
// 返回数据和方法
return { elementOpacity, scrollThreshold, keyword, searchBtn, rankList,newList, setnum, qcodeVisible, layerVisible, content, saveBtn, rankDj,goLink,changeBtn,copyText,drawer,selectBtn,is_m };
return { elementOpacity, scrollThreshold, keyword, searchBtn, rankList,newList, setnum, qcodeVisible, layerVisible, content, saveBtn, rankDj,goLink,changeBtn,copyText,drawer,selectBtn,is_m,QList,QLoading,QStatus,total_result };
}
})
.use(ElementPlus) // 使用 Element Plus
+34 -23
View File
@@ -76,47 +76,58 @@
</div>
{/notempty}
<div class="btns">
<div class="btn btnCol" @click.stop="copyText($event,'{$detail.title}','{$detail.url}','{$detail.code}')"><i class="iconfont icon-fenxiang1"></i>复制分享</div>
<div class="btn btnCol" @click.stop="copyText($event,'{$detail.title|trim}','{$detail.url}','{$detail.code}')"><i class="iconfont icon-fenxiang1"></i>复制分享</div>
<a href="{$detail.url}" target="_blank" class="btn"><i class="iconfont icon-yun_o"></i>立即访问</a>
</div>
</div>
<h3 class="samelistNav">相关资源</h3>
<div class="box details samelistBox">
<div class="samelist">
{volist name="sameList" id="vo"}
<a href="/d/{$vo.source_id}.html" class="item">
<p>
<span>{$key+1}</span>
{$vo.title}
</p>
</a>
{/volist}
</div>
</div>
</div>
<div class="right">
<block v-for="(item,index) in rankList" :key="index">
{volist name="hotList" id="vo"}
<div class="nav">
<img :src="item.image" v-if="item.image">
{{item.name}}
{notempty name="vo.image"}
<img src="{$vo.image}" :alt="{$vo.name}">
{/notempty}
{$vo.name}
</div>
<div class="box" v-if="item.list && item.list.length>0">
<div class="list">
<a :href="'/s/'+vo.title+'.html'" v-for="(vo,i) in item.list" :key="i" class="item" v-show="i<5">
<div class="box">
<div class="list">
{volist name="vo.list" id="vos" length="5"}
<a href="/s/{$vos.title}.html" class="item">
<p>
<span>{{i+1}}</span>
{{vo.title}}
<span>{$key+1}</span>
{$vos.title}
</p>
</a>
{/volist}
</div>
</div>
</block>
{/volist}
</div>
</div>
{include file="common/foot"}
</div>
{include file="common/footer"}
<script type="text/javascript" charset="utf-8">
if(!app.is_m){
app.rankList = JSON.parse('<?php echo json_encode($rankList, JSON_UNESCAPED_UNICODE); ?>');
for (const item of app.rankList) {
axios.get('/api/tool/ranking',{
params: {
channel: item.name,
is_m: app.is_m
}
})
.then(function (res) {
item.list = res.data.data
})
}
app.rankList = JSON.parse('<?php echo json_encode($rankList, JSON_UNESCAPED_UNICODE); ?>');
for (const item of app.rankList) {
axios.get('/api/tool/ranking',{
params: {
channel: item.name
}
})
}
</script>
</body>
+39 -25
View File
@@ -36,78 +36,92 @@
<div class="content">
{if $config.ranking_type == 1 }
<div class="list">
<a :href="'/s/'+vo.title+'.html'" target="_blank" class="item" v-for="(vo,i) in newList" :key="i">
{volist name="newList" id="vos"}
{if condition="strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false || $key < $config.ranking_m_num"}
<a href="/s/{$vos.title}.html" target="_blank" class="item">
<div class="img">
<img :src="vo.src" :alt="vo.title"/>
<img src="{$vos.src}" alt="{$vos.title}" />
<span>Loading...</span>
</div>
<p>{{vo.title}}</p>
<p>{$vos.title}</p>
</a>
{/if}
{/volist}
</div>
{else /}
<div class="list">
<a :href="'/s/'+vo.title+'.html'" target="_blank" class="item" v-for="(vo,i) in newList" :key="i">
{volist name="newList" id="vos"}
{if condition="strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false || $key < $config.ranking_m_num"}
<a href="/s/{$vos.title}.html" target="_blank" class="item">
<p>
<span>{{i+1}}</span>
{{vo.title}}
<span>{$key+1}</span>
{$vos.title}
</p>
</a>
{/if}
{/volist}
</div>
{/if}
</div>
</div>
<div class="block" v-for="(item,index) in rankList" :key="index">
{volist name="hotList" id="vo"}
<div class="block">
<div class="nav">
<img :src="item.image" :alt="item.name" v-if="item.image">
{{item.name}}
{notempty name="vo.image"}
<img src="{$vo.image}" :alt="{$vo.name}">
{/notempty}
{$vo.name}
</div>
<div class="content">
{if $config.ranking_type == 1 }
<div class="list">
<a :href="'/s/'+vo.title+'.html'" target="_blank" class="item" v-for="(vo,i) in item.list" :key="i">
{volist name="vo.list" id="vos"}
{if condition="strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false || $key < $config.ranking_m_num"}
<a href="/s/{$vos.title}.html" target="_blank" class="item">
<div class="img">
<img :src="vo.src" :alt="vo.title" />
<img src="{$vos.src}" alt="{$vos.title}" />
<span>Loading...</span>
</div>
<p>{{vo.title}}</p>
<p>{$vos.title}</p>
</a>
{/if}
{/volist}
</div>
{else /}
<div class="list">
<a :href="'/s/'+vo.title+'.html'" target="_blank" class="item" v-for="(vo,i) in item.list" :key="i">
{volist name="vo.list" id="vos"}
{if condition="strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false || $key < $config.ranking_m_num"}
<a href="/s/{$vos.title}.html" target="_blank" class="item">
<p>
<span>{{i+1}}</span>
{{vo.title}}
<span>{$key+1}</span>
{$vos.title}
</p>
</a>
{/if}
{/volist}
</div>
{/if}
</div>
</div>
{/volist}
</div>
</div>
{include file="common/foot"}
</div>
{include file="common/footer"}
<script type="text/javascript" charset="utf-8">
app.newList = JSON.parse('<?php echo json_encode($newList, JSON_UNESCAPED_UNICODE); ?>');
let num = '{$config.ranking_m_num}';
if(app.is_m){
app.newList = app.newList.slice(0, num);
}
app.rankList = JSON.parse('<?php echo json_encode($rankList, JSON_UNESCAPED_UNICODE); ?>');
for (const item of app.rankList) {
axios.get('/api/tool/ranking',{
params: {
channel: item.name,
is_m: app.is_m
channel: item.name
}
})
.then(function (res) {
item.list = res.data.data
})
}
</script>
</body>
+102 -25
View File
@@ -32,9 +32,50 @@
</div>
</div>
<div class="left">
<h3>为您找到【<span>{$keyword}</span>】相关资源<span> {$list.total_result} </span></h3>
<div class="box">
<h3 v-if="total_result>0">为您找到【<span>{$keyword}</span>】相关资源<span>&nbsp;{{total_result}}&nbsp;</span></h3>
<h3 v-else>为您找到【<span>{$keyword}</span>】相关资源<span>&nbsp;{$list.total_result}&nbsp;</span></h3>
{if condition="$config.is_quan == 1"}
<div class="Ebox" v-if="QStatus">
<div class="Qloading" v-if="QLoading">
<div class="loader"></div>
</div>
<div class="list">
<div class="item" v-for="(item,index) in QList" :key="index">
<a target="_blank" :href="item.url" class="title">
{{item.title}}
</a>
<div class="type time">30分钟后自动删除,请及时保存</div>
<div class="type">
<span>来源:网络资源</span>
</div>
<div class="btns">
<div class="btn" @click.stop="copyText($event,item.title,item.url,'')"><i class="iconfont icon-fenxiang1"></i>复制分享</div>
<a :href="'/d/'+item.id+'.html'" class="btn"><i class="iconfont icon-fangwen"></i>查看详情</a>
<a target="_blank" :href="item.url" class="btn">
<img src="/static/index/images/0.png" class="icon" />
立即访问
</a>
</div>
</div>
</div>
<block v-if="!QLoading && QList.length==0">
<div class="Qtips" v-if="QStatus==1">
<p>未找到更多的资源~</p>
</div>
<el-empty v-else style="margin-top: 10%;padding-bottom: 10%;" :image-size="200" image="{$config.search_bg??''}" description="{$config.search_tips|default='未找到,可换个关键词尝试哦~'}"></el-empty>
</block>
</div>
{/if}
<div class="box" v-if="QStatus!=2">
{if condition="$list.total_result>0"}
{if condition="$config.is_quan == 1"}
<div class="Qbtn">
<div class="btn" onclick="getMore()">以下资源不是你想要的?点击尝试获取新资源</div>
</div>
{/if}
<div class="list">
{foreach $list.items as $key=>$vo }
<div class="item">
@@ -59,7 +100,7 @@
{/notempty}
</div>
<div class="btns">
<div class="btn" @click.stop="copyText($event,'{$vo.title}','{$vo.url}','{$vo.code}')"><i class="iconfont icon-fenxiang1"></i>复制分享</div>
<div class="btn" @click.stop="copyText($event,'{$vo.title|trim}','{$vo.url}','{$vo.code}')"><i class="iconfont icon-fenxiang1"></i>复制分享</div>
<a href="/d/{$vo.id}.html" class="btn"><i class="iconfont icon-fangwen"></i>查看详情</a>
<a target="_blank" href="{$vo.url}" class="btn">
<img src="/static/index/images/{$vo.is_type}.png" class="icon" />
@@ -80,42 +121,78 @@
</div>
</div>
<div class="right">
<block v-for="(item,index) in rankList" :key="index">
{volist name="hotList" id="vo"}
<div class="nav">
<img :src="item.image" v-if="item.image">
{{item.name}}
{notempty name="vo.image"}
<img src="{$vo.image}" :alt="{$vo.name}">
{/notempty}
{$vo.name}
</div>
<div class="box" v-if="item.list && item.list.length>0">
<div class="list">
<a :href="'/s/'+vo.title+'.html'" v-for="(vo,i) in item.list" :key="i" class="item" v-show="i<5">
<div class="box">
<div class="list">
{volist name="vo.list" id="vos" length="5"}
<a href="/s/{$vos.title}.html" class="item">
<p>
<span>{{i+1}}</span>
{{vo.title}}
<span>{$key+1}</span>
{$vos.title}
</p>
</a>
{/volist}
</div>
</div>
</block>
{/volist}
</div>
</div>
{include file="common/foot"}
</div>
{include file="common/footer"}
<script type="text/javascript" charset="utf-8">
// if(!app.is_m){
app.rankList = JSON.parse('<?php echo json_encode($rankList, JSON_UNESCAPED_UNICODE); ?>');
for (const item of app.rankList) {
axios.get('/api/tool/ranking',{
params: {
channel: item.name,
is_m: app.is_m
}
})
.then(function (res) {
item.list = res.data.data
})
app.rankList = JSON.parse('<?php echo json_encode($rankList, JSON_UNESCAPED_UNICODE); ?>');
for (const item of app.rankList) {
axios.get('/api/tool/ranking',{
params: {
channel: item.name
}
})
}
var status
if({$list.total_result} == 0){
if('{$config.is_quan}' == 1) {
//默认资源没有直接执行
status = 2
app.QStatus = status
getAll()
}
// }
}
function getMore(){
if('{$config.is_quan}' == 1){
status = 1
app.QStatus = status
getAll()
}
}
function getAll() {
if(app.QStatus==0 || app.QLoading || '{$config.is_quan}' != 1) return
app.QLoading = true
axios.post('/api/tool/Qsearch',{
title: app.keyword
})
.then(function (res) {
app.QLoading = false
app.QStatus = status
app.QList = res.data.data
app.total_result = {$list.total_result} + app.QList.length
})
.catch(function (error) {
app.QLoading = false;
app.QStatus = status
app.QList = []
app.total_result = {$list.total_result} + app.QList.length
});
}
</script>
</body>
</html>
+10 -5
View File
@@ -29,14 +29,14 @@
<el-table-column label="是否每日更新" width="150" align="center">
<template slot-scope="scope">
<el-switch v-model="scope.row.is_update==1?true:false"
@change="clickStatus(scope.row)">
@change="clickStatus(scope.row,1)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="是否前台展示" width="150" align="center">
<template slot-scope="scope">
<el-switch v-model="scope.row.status==1?false:true"
@change="clickStatus(scope.row)">
@change="clickStatus(scope.row,0)">
</el-switch>
</template>
</el-table-column>
@@ -228,10 +228,15 @@ console.log(error);
}).catch(() => {
});
},
clickStatus(row) {
clickStatus(row,type) {
var that = this;
axios.post(row.status ? '/admin/source_category/enable' : '/admin/source_category/disable', Object.assign({}, PostBase, {
source_category_id: row.source_category_id
let url = row.status ? '/admin/source_category/enable' : '/admin/source_category/disable'
if(type==1){
url = !row.is_update ? '/admin/source_category/enable' : '/admin/source_category/disable'
}
axios.post(url, Object.assign({}, PostBase, {
source_category_id: row.source_category_id,
type: type
}))
.then(function (response) {
that.getList();
+47 -2
View File
@@ -8,6 +8,7 @@
<el-form :inline="true" @submit.native.prevent>
<el-form-item>
<el-button icon="el-icon-plus" size="small" @click="clickAdd" plain>添加资源</el-button>
<el-button icon="el-icon-delete" size="small" @click="postMultDelete" plain>批量删除</el-button>
<el-button icon="el-icon-document-copy" size="small" @click="getExport" plain>导出资源</el-button>
<el-button icon="el-icon-plus" size="small" @click="ImportShow" plain>夸克导入专用</el-button>
<el-button icon="el-icon-plus" size="small" @click="ImportBatch" plain>批量导入</el-button>
@@ -29,7 +30,9 @@
</el-form-item>
</div>
</el-form>
<el-table :data="dataList.data" v-loading="loading">
<el-table :data="dataList.data" @selection-change="changeSelection" v-loading="loading">
<el-table-column type="selection" width="50">
</el-table-column>
<el-table-column prop="source_id" label="ID" width="80">
</el-table-column>
<el-table-column prop="title" label="资源名称">
@@ -146,7 +149,7 @@
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<span style="color: #999;">导入夸克官方导出的csv文件</span>
<span style="color: #999;">使用xlsx格式,第一列资源名称 第二列资源地址</span>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@@ -214,6 +217,7 @@ https://drive.uc.cn/s/xxxxxxxxxxx"
dialogFormAdd: false,
dialogFormEdit: false,
loading: true,
selectList: [],
dataList: [],
form: {
page: 1,
@@ -468,6 +472,47 @@ https://drive.uc.cn/s/xxxxxxxxxxx"
window.open(url);
that.$message.success('数据导出成功');
},
postMultDelete() {
var that = this;
if (that.selectList.length == 0) {
that.$message.error('未选择任何资源!');
return;
}
this.$confirm('即将删除选中的资源, 是否确认?', '批量删除', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
axios.post('/admin/source/delete', Object.assign({}, PostBase, {
source_id: that.selectList.join(",")
}))
.then(function (response) {
that.getList();
if (response.data.code == CODE_SUCCESS) {
that.$message({
message: response.data.message,
type: 'success'
});
} else {
that.$message.error(response.data.message);
}
})
.catch(function (error) {
that.$message.error('服务器内部错误');
console.log(error);
});
}).catch(() => {
});
},
changeSelection(list) {
var that = this;
that.selectList = [];
for (var index in list) {
that.selectList.push(list[index].source_id);
}
},
}
})
</script>
+2
View File
@@ -164,6 +164,8 @@ INSERT INTO `qf_conf` VALUES (48, 'search_tips', '', '未搜索提示词', '为
INSERT INTO `qf_conf` VALUES (49, 'search_bg', '', '未搜索提示图', '', 0, 4, NULL, 1, 1, 0, 1, 1726108851, 1726108851);
INSERT INTO `qf_conf` VALUES (50, 'home_new', '1', '最新列表', '仅无图模式有效', 0, 2, '开启=>0\n关闭=>1', 3, 1, 79, 1, 1726299605, 1726299605);
INSERT INTO `qf_conf` VALUES (51, 'home_new_img', '', '最新图标', '', 0, 4, NULL, 3, 1, 79, 1, 1726302688, 1726302688);
INSERT INTO `qf_conf` VALUES (52, 'is_quan', '0', '全网搜', '', 0, 2, '关闭=>0\n开启=>1', 1, 1, 1, 1, 1729928547, 1729928547);
-- ----------------------------
-- Table structure for qf_days
+93 -1
View File
@@ -434,7 +434,7 @@ a:hover {
.listBox h3:after{
content: "";
position: absolute;
bottom: 18px;
bottom: 20px;
left: 0;
width: 4px;
background-color: var(--theme-theme);
@@ -462,6 +462,7 @@ a:hover {
.listBox .left{
width: 0;
flex: 1;
overflow: hidden;
}
.listBox .left .list{
padding: 0 15px;
@@ -614,6 +615,7 @@ a:hover {
.listBox .details{
padding: 40px 50px;
min-height: inherit;
}
.details .pic{
@@ -739,6 +741,96 @@ a:hover {
display: none;
}
.loader {
margin: auto;
width: fit-content;
font-weight: bold;
font-family: monospace;
font-size: 24px;
background: radial-gradient(circle closest-side,var(--theme-theme) 94%,#0000) right/calc(200% - 1em) 100%;
animation: l24 1s infinite alternate linear;
}
.loader::before {
content: "全网检索中,请稍等...";
line-height: 1em;
color: #0000;
background: inherit;
background-image: radial-gradient(circle closest-side,#fff 94%,var(--theme-theme));
-webkit-background-clip:text;
background-clip:text;
}
@keyframes l24{
100%{background-position: left}
}
.listBox .Ebox{
border-radius: 12px;
background-color: var(--theme-other_background);
box-shadow: 0 4px 10px rgba(225, 225, 225, 0.3);
padding: 15px;
margin-bottom: 15px;
}
.listBox .left .Ebox .list .item:last-child{
border-bottom: none;
}
.Qtips{
text-align: center;
font-size: 14px;
color: #999;
}
.Qbtn{
text-align: center;
padding-top: 10px;
}
.Qbtn .btn{
display: inline-block;
color: #999999;
cursor: pointer;
}
/*//相关资源*/
.listBox .details.samelistBox{
padding: 25px 30px;
}
.samelist {
display: flex;
flex-wrap: wrap;
}
.samelist .item {
width: 50%;
margin: 0;
border-radius: 0;
text-align: left;
padding: 3px 0;
}
.samelist .item p {
margin-top: 0;
padding: 4px 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.samelist .item:hover{
transform: translateY(0);
}
.samelist .item p span {
color: #c4c7ce;
margin-right: 6px;
}
.samelist .item:nth-child(1) p span {
color: #FE2D46;
}
.samelist .item:nth-child(2) p span {
color: #FF6600;
}
.samelist .item:nth-child(3) p span {
color: #FAA90E;
}
+47 -1
View File
@@ -176,6 +176,28 @@
text-align: justify;
min-height: calc(100% - 50vw);
}
.listBox .Ebox{
padding: 0 4vw 5vw;
margin-top: 4vw;
margin-bottom: 4vw;
text-align: justify;
}
.listBox .left .Ebox .list .item:last-child{
padding: 5vw 0 0;
}
.Qloading {
padding-top: 6vw;
}
.Qtips{
padding-top: 5.6vw;
}
.loader{
font-size: 18px;
}
.Qbtn{
padding-top: 5vw;
}
.listBox .left .list {
padding: 0;
}
@@ -274,14 +296,38 @@
.searchDetail .search input {
padding-left: 5vw;
}
.detailBox{
min-height: inherit;
}
.detailBox h3{
display: none;
}
.detailBox .details {
padding-top: 5vw;
margin-top: 20vw;
min-height: inherit;
}
.listBox .details.samelistBox{
padding: 3vw;
margin-top: 0;
}
.detailBox h3.samelistNav{
display: block;
position: relative;
padding: 30px 0 17px 15px;
font-size: 4.2vw;
text-align: left;
}
.detailBox h3.samelistNav:after {
display: block;
}
.samelist .item {
width: 100%;
}
.samelist .item p {
padding: 4px 6px;
}
+8
View File
@@ -0,0 +1,8 @@
更新方式
1、app文件夹整包上传覆盖
2、替换 public\static\index\css\app.css和public\static\index\css\m.css
3、执行sql语句
INSERT INTO `qf_conf` VALUES (52, 'is_quan', '0', '全网搜', '', 0, 2, '关闭=>0\n开启=>1', 1, 1, 1, 1, 1729928547, 1729928547);
如何对接微信对话功能
详情见 app\api\controller\Chatbot.php