<?php
namespace app\manage\controller;
use \app\base\controller\ManageControllerTrait;
class FindController extends \app\base\controller\BaseController
{
    use ManageControllerTrait;

	public function index(){


		$this->checkManageSession();

		$this->categories = $this->getGoodsCategories();
		$this->navs = $this->getFindNavs();
		$apiConf = \app\common\ConfigStore::load('api');
		$this->juheKeys = array(
		    'key235' => isset($apiConf['juhe_235_key']) ? $apiConf['juhe_235_key'] : '',
		    'key850' => isset($apiConf['juhe_850_key']) ? $apiConf['juhe_850_key'] : '',
		);
		$this->juheTypes235 = \app\common\JuheService::types235();
		$this->juheTypes850 = \app\common\JuheService::types850();
		$this->pageText=array("发现管理","文章列表");
		$where[] = "1";

    	$keyword = $this->arg("keyword", '');
    	if($keyword){
    		$where[] = "`title` LIKE '%" . addslashes($keyword) . "%'";
    	}

    	$navid = intval($this->arg("navid", 0));
    	if($navid > 0){
    		$where[] = "`navid` = {$navid}";
    	}

    	$baseUrl = "index.php?r=manage/find/index";
    	if($keyword){
    		$baseUrl .= "&keyword=" . urlencode($keyword);
    	}
    	if($navid > 0){
    		$baseUrl .= "&navid=" . $navid;
    	}

        $page = obj('api/ApiData')->page("50", "yun_article", $where, "`id` DESC", $baseUrl);
        $this->page = $page;
        $this->ret = $page['list'];
        $this->keyword = $keyword;
        $this->navid = $navid;
		$this->display();
	}

	/**
	 * 一键采集（好单库朋友圈素材库）
	 * 拉取朋友圈发圈文案，生成包含产品卡片（券后价/优惠价/图片/去购买）的文章入库 yun_article
	 */
	public function collect(){
        $this->checkManageSession();
        set_time_limit(0);

        $api = \app\common\ConfigStore::load('api');
        $dtkAppKey = $api['dtk_appkey'] ?? '';
        $dtkAppSecret = $api['dtk_appsecret'] ?? '';
        $hdkApiKey = $api['hdk_appkey'] ?? '';

        if (empty($hdkApiKey)) {
            exit(json_encode(array("info" => "请先在后台配置好单库API(key)", "status" => "n")));
        }

        $tjk = new \ZhiCms\ext\Tjk([
            'DtkappKey' => $dtkAppKey,
            'DtkappSecret' => $dtkAppSecret,
            'HdkApiKey' => $hdkApiKey,
        ]);
        $hdk = $tjk->getHdk();
        if (!$hdk) {
            exit(json_encode(array("info" => "好单库API未配置", "status" => "n")));
        }
        $dtk = $tjk->getDtk();

        $cid = 0; // 朋友圈采集不使用电商产品分类(cid)，仅用发现分类(navid)
        $navid = intval($this->arg("navid", 0));
        $pages = max(1, intval($this->arg("pages", 5)));
        $minId = intval($this->arg("min_id", 1));

        // 持久化本次采集参数，供「计划任务-朋友圈采集」定时跑时复用
        $apiTmp = \app\common\ConfigStore::load('api');
        if (!is_array($apiTmp)) $apiTmp = array();
        $apiTmp['moments_navid'] = $navid;
        $apiTmp['moments_pages'] = $pages;
        \app\common\ConfigStore::save('api', $apiTmp);

        $success = 0;
        $update = 0;
        $pageDone = 0;
        $lastMinId = $minId;

        for ($p = 0; $p < $pages; $p++) {
            $res = $hdk->FriendsCircleItems($minId);
            if ($res['code'] != 1) {
                break;
            }
            $list = $res['data'];
            if (empty($list) || !is_array($list)) {
                break;
            }

            foreach ($list as $item) {
            	$itemid = $item['items']['itemid'] ?? ($item['itemid'] ?? '');
            	if (empty($itemid)) {
            		continue;
            	}

            	$title = trim($item['items']['itemshorttitle'] ?? '');
            	$mainPic = $item['items']['itempic'] ?? '';
            	$commentHtml = $item['comment']['copy_content'] ?? '';
            	$descText = $item['items']['itemdesc'] ?? '';

            	// 处理朋友圈文案中的 emoji/淘口令占位符，避免 [emoji:xxx] 污染正文
            	if ($commentHtml !== '') {
            		$commentHtml = $hdk->ProcessEmoji($commentHtml);
            	}
            	if ($descText !== '') {
            		$descText = $hdk->ProcessEmoji($descText);
            	}

            	$contentText = '';
            	if ($commentHtml !== '') {
            		$contentText .= '<p>' . $commentHtml . '</p>';
            	}
            	if ($descText !== '') {
            		$contentText .= '<p>' . $descText . '</p>';
            	}
            	$contentText .= $this->buildGoodsMarker($dtk, $item);

            	$decText = strip_tags($commentHtml);
            	$decText = mb_substr($decText, 0, 120, 'UTF-8');

            	$data = [
            		'goodsId' => $itemid,
            		'itemLink' => $item['items']['couponurl'] ?? ('https://item.taobao.com/item.htm?id=' . $itemid),
            		'title' => $title,
            		'content' => $contentText,
            		'cid' => $cid,
            		'navid' => $navid,
            		'mainPic' => $mainPic,
            		'keywords' => $item['items']['itemshorttitle'] ?? '',
            		'dec' => $decText,
            		'view' => 0,
            		'like' => 0,
            		'lock' => 0,
            		'status' => 1,
            		'couponEndTime' => '',
            		'date' => date("Y-m-d H:i:s", time()),
            	];
            	// 商品采集（朋友圈素材）入库：作者默认调用当前管理员昵称/头像（前台展示用）
            	$data['author'] = isset($_SESSION['manage_nickname']) && $_SESSION['manage_nickname'] !== ''
            		? $_SESSION['manage_nickname']
            		: (isset($_SESSION['manage_system']) ? $_SESSION['manage_system'] : '管理员');
            	$data['author_pic'] = isset($_SESSION['manage_pic']) ? $_SESSION['manage_pic'] : '';
            	// 重复不再丢弃，按 goodsId 更新已有记录（手动/自动任务一致）
            	if ($this->saveArticleUpsert($data, $itemid) === 'update') {
            		$update++;
            	} else {
            		$success++;
            	}
            }

            $pageDone++;

            // 获取下一页游标，如果未变化说明到最后一页了
            $nextMinId = intval($res['min_id'] ?? 0);
            if ($nextMinId > 0 && $nextMinId != $minId) {
            	$lastMinId = $minId;
            	$minId = $nextMinId;
            } else {
            	break;
            }

            // 页间稍歇，避免频繁请求被限
            usleep(300000);
            }

            exit(json_encode(array(
            "info" => "采集完成：翻页 {$pageDone} 次，成功入库 {$success} 条，更新 {$update} 条",
            "status" => "y"
            )));
            }

	/**
	 * 朋友圈采集（计划任务专用）：读取后台保存的分类参数，定时跑无需传参。
	 * 复用 collect() 的好单库朋友圈素材库采集逻辑。
	 * @return array ['ok'=>bool,'output'=>string]
	 */
	public function collectCron(){
		set_time_limit(0);
		$api = \app\common\ConfigStore::load('api');
		$cid   = 0; // 朋友圈采集不使用电商产品分类(cid)，仅用发现分类(navid)
		$navid = isset($api['moments_navid']) ? intval($api['moments_navid']) : 0;
		$pages = isset($api['moments_pages']) ? max(1, intval($api['moments_pages'])) : 3;
		$minId = 1;

		// 计划任务无管理员 SESSION，取默认管理员（第一条）作为文章作者头像/昵称
		$defaultAuthor = '管理员';
		$defaultAuthorPic = '';
		$admRows = obj("api/ApiData")->dataSelect("yun_manage", array(), "id ASC");
		if (!empty($admRows) && is_array($admRows)) {
			$adm = isset($admRows[0]) ? $admRows[0] : $admRows;
			if (!empty($adm['nickname'])) $defaultAuthor = $adm['nickname'];
			if (!empty($adm['pic'])) $defaultAuthorPic = $adm['pic'];
		}

		$hdkApiKey = $api['hdk_appkey'] ?? '';
		if (empty($hdkApiKey)) {
			return array('ok' => false, 'output' => '请先在后台配置好单库API(key)，方可采集朋友圈');
		}
		// 未显式配置归属分类时，自动取第一个发现分类作为兜底，避免“未设置 navid”导致无法采集
		if ($navid <= 0) {
			$navs = $this->getFindNavs();
			if (!empty($navs)) {
				$navid = intval(key($navs));
				// 兜底值写回配置，便于下次直接复用
				$api['moments_navid'] = $navid;
				\app\common\ConfigStore::save('api', $api);
			}
		}
		if ($navid <= 0) {
			return array('ok' => false, 'output' => '未设置朋友圈文章归属分类(navid)，请在计划任务-朋友圈采集中选择发现分类后保存，或先在发现管理添加发现分类');
		}

		$tjk = new \ZhiCms\ext\Tjk(array(
			'DtkappKey'    => $api['dtk_appkey']    ?? '',
			'DtkappSecret' => $api['dtk_appsecret'] ?? '',
			'HdkApiKey'    => $hdkApiKey,
		));
		$hdk = $tjk->getHdk();
		if (!$hdk) return array('ok' => false, 'output' => '好单库API未配置');

		$success = 0; $update = 0; $pageDone = 0;
		for ($p = 0; $p < $pages; $p++) {
			$res = $hdk->FriendsCircleItems($minId);
			if ($res['code'] != 1) break;
			$list = $res['data'];
			if (empty($list) || !is_array($list)) break;

			foreach ($list as $item) {
				$itemid = $item['items']['itemid'] ?? ($item['itemid'] ?? '');
				if (empty($itemid)) continue;

				$title = trim($item['items']['itemshorttitle'] ?? '');
				$mainPic = $item['items']['itempic'] ?? '';
				$commentHtml = $item['comment']['copy_content'] ?? '';
				$descText = $item['items']['itemdesc'] ?? '';
				if ($commentHtml !== '') $commentHtml = $hdk->ProcessEmoji($commentHtml);
				if ($descText !== '')    $descText = $hdk->ProcessEmoji($descText);
				$contentText = '';
				if ($commentHtml !== '') $contentText .= '<p>' . $commentHtml . '</p>';
				if ($descText !== '')    $contentText .= '<p>' . $descText . '</p>';
				// 商品卡标记：与手动采集一致，确保朋友圈文章带 [ZhiCmsUrl] 站内商品卡
				$contentText .= $this->buildGoodsMarker($tjk, $item);

				$data = array(
					'goodsId'  => $itemid,
					'itemLink' => $item['items']['couponurl'] ?? ('https://item.taobao.com/item.htm?id=' . $itemid),
					'title'    => $title,
					'content'  => $contentText,
					'cid'      => $cid,
					'navid'    => $navid,
					'mainPic'  => $mainPic,
					'keywords' => $title,
					'dec'      => mb_substr(strip_tags($commentHtml), 0, 120, 'UTF-8'),
					'view'     => 0,
					'like'     => 0,
					'lock'     => 0,
					'status'   => 1,
					'couponEndTime' => '',
					'date'     => date("Y-m-d H:i:s", time()),
				);
				// 计划任务商品采集入库：作者默认调用管理员昵称/头像（前台展示用）
				$data['author'] = $defaultAuthor;
				$data['author_pic'] = $defaultAuthorPic;
				// 重复不再丢弃，按 goodsId 更新已有记录
				if ($this->saveArticleUpsert($data, $itemid) === 'update') {
					$update++;
				} else {
					$success++;
				}
			}
			$pageDone++;
			$nextMinId = intval($res['min_id'] ?? 0);
			if ($nextMinId > 0 && $nextMinId != $minId) { $minId = $nextMinId; } else { break; }
			usleep(300000);
		}
		return array('ok' => true, 'output' => "朋友圈采集完成：翻页{$pageDone}次，成功入库{$success}条，更新{$update}条");
	}

	/**
	 * 大淘客朋友圈采集（新增数据源，与好单库朋友圈 collect() 并列，互不影响）
	 * 接口：https://openapi.dataoke.com/api/goods/friends-circle-list
	 * 入参：version=1.3.0, pageId(分页), pageSize(每页条数), sort(0-6), cid(分类id)
	 * cid 与网站现有分类一致：前端下拉选网站分类，其 id 即作为大淘客 cid 传入
	 */
	public function collectDtk(){
		$this->checkManageSession();
		set_time_limit(0);

		$api = \app\common\ConfigStore::load('api');
		$dtkAppKey = $api['dtk_appkey'] ?? '';
		$dtkAppSecret = $api['dtk_appsecret'] ?? '';
		if (empty($dtkAppKey) || empty($dtkAppSecret)) {
			exit(json_encode(array("info" => "请先在后台配置大淘客 AppKey/AppSecret", "status" => "n")));
		}

		$tjk = new \ZhiCms\ext\Tjk(array(
			'DtkappKey'    => $dtkAppKey,
			'DtkappSecret' => $dtkAppSecret,
			'HdkApiKey'    => $api['hdk_appkey'] ?? '',
		));
		$dtk = $tjk->getDtk();
		if (!$dtk) {
			exit(json_encode(array("info" => "大淘客API未配置", "status" => "n")));
		}

		// 分类映射：商品分类(cid，与网站商品分类一致) => 文章分类(navid，网站发现分类)
		// 参考资讯采集：用户在前端对每个商品分类选一个文章分类并勾选「采集」，手动指定，不做任何自动映射
		$mapRaw = isset($_POST['map']) ? $_POST['map'] : $this->arg("map", '');
		if (is_string($mapRaw) && $mapRaw !== '') {
			$mapRaw = html_entity_decode($mapRaw, ENT_QUOTES);
			$mapArr = json_decode($mapRaw, true);
		} else {
			$mapArr = is_array($mapRaw) ? $mapRaw : array();
		}
		$map = array(); // cid(int) => navid(int)
		if (!empty($mapArr) && is_array($mapArr)) {
			foreach ($mapArr as $k => $v) {
				$cid = intval($k); $nav = intval($v);
				if ($cid > 0 && $nav > 0) $map[$cid] = $nav;
			}
		}
		if (empty($map)) {
			exit(json_encode(array("info" => "请至少选择一个商品分类，并指定其归属的文章分类", "status" => "n")));
		}

		$sort = intval($this->arg("sort", 0));
		if ($sort < 0 || $sort > 6) $sort = 0;
		$pages = max(1, intval($this->arg("pages", 5)));
		$pageSize = max(1, min(100, intval($this->arg("pageSize", 50))));

		// 持久化参数，供「计划任务-大淘客朋友圈采集」复用（保存 map + sort + pages）
		$apiTmp = \app\common\ConfigStore::load('api');
		if (!is_array($apiTmp)) $apiTmp = array();
		$apiTmp['dtk_moments_map']  = $map;
		$apiTmp['dtk_moments_sort'] = $sort;
		$apiTmp['dtk_moments_pages']= $pages;
		\app\common\ConfigStore::save('api', $apiTmp);

		$success = 0; $update = 0; $pageDone = 0;
		$errCids = array(); // 记录拉取异常的商品分类，避免单分类失败中断整体采集
		// 外层：按用户选择的商品分类逐个拉取，每轮文章归入该 cid 对应的文章分类 navid
		foreach ($map as $loopCid => $navidStore) {
			$pageId = '';
		for ($p = 0; $p < $pages; $p++) {
			$res = $dtk->FriendsCircleList($pageId, $pageSize, $sort, $loopCid);
			// 大淘客 FriendsCircleList 成功约定 code==1（见 Dtk::FriendsCircleList 第354行），
			// 与好单库 collect 一致；此处必须判定 code!=1 才是失败
			if (empty($res['code']) || $res['code'] != 1) {
				$msg = $res['message'] ?? '大淘客朋友圈接口返回异常';
				$errCids[$loopCid] = "商品分类#{$loopCid} 第" . ($p + 1) . "页：" . $msg;
				break; // 该分类拉取失败：跳过此分类，继续下一个分类
			}
			$list = $res['items'];
			if (empty($list) || !is_array($list)) break;

			foreach ($list as $item) {
				$itemid = $item['goodsId'] ?? '';
				if (empty($itemid)) continue;

				$title = trim($item['title'] ?? '');
				$dtitle = trim($item['dtitle'] ?? '');
				$mainPic = $item['mainPic'] ?? '';
				// 商品描述（desc）作为正文主内容
				$desc = trim($item['desc'] ?? '');
				if ($desc !== '') {
					$desc = $this->cleanMomentsText($desc);
				}
				// 发圈文案（朋友圈专属字段 circleText），清洗占位符（口令/emoji 标签）
				$circleText = $item['circleText'] ?? '';
				if ($circleText !== '') {
					$circleText = $this->cleanMomentsText($circleText);
				}

				// 内容组装：desc 是主内容；circleText 与 desc 不同（去空白后不一致）时，
				// 作为「发圈文案」补充拼到内容里，避免与 desc 重复。
				$contentText = '';
				if ($desc !== '') {
					$contentText .= '<p>' . $desc . '</p>';
				}
				if ($circleText !== '' && trim(preg_replace('/\s+/u', '', $circleText)) !== trim(preg_replace('/\s+/u', '', $desc))) {
					$contentText .= '<p class="circle-text">' . $circleText . '</p>';
				}
				// 商品卡标记：大淘客朋友圈商品平台固定淘宝(tb)，复用 buildGoodsMarker
				$contentText .= $this->buildGoodsMarker($dtk, $item);

				// 摘要：优先 desc，其次 circleText，截断 120 字
				$decSource = $desc !== '' ? $desc : $circleText;
				$decText = strip_tags($decSource);
				$decText = mb_substr($decText, 0, 120, 'UTF-8');

				// 关键词：综合标题、短标题、描述，去重后用空格连接（利于站内搜索）
				$kwParts = array_unique(array_filter(array($title, $dtitle, $desc)));
				$keywords = implode(' ', $kwParts);

				$data = array(
					'goodsId'  => $itemid,
					'itemLink' => $item['itemLink'] ?? ('https://item.taobao.com/item.htm?id=' . $itemid),
					'title'    => $title,
					'content'  => $contentText,
					'cid'      => 0,
					'navid'    => $navidStore,
					'mainPic'  => $mainPic,
					'keywords' => $keywords,
					'dec'      => $decText,
					'view'     => 0,
					'like'     => 0,
					'lock'     => 0,
					'status'   => 1,
					'couponEndTime' => '',
					'date'     => date("Y-m-d H:i:s", time()),
				);
				// 作者默认调用当前管理员昵称/头像（前台展示用）
				$data['author'] = isset($_SESSION['manage_nickname']) && $_SESSION['manage_nickname'] !== ''
					? $_SESSION['manage_nickname']
					: (isset($_SESSION['manage_system']) ? $_SESSION['manage_system'] : '管理员');
				$data['author_pic'] = isset($_SESSION['manage_pic']) ? $_SESSION['manage_pic'] : '';

				if ($this->saveArticleUpsert($data, $itemid) === 'update') {
					$update++;
				} else {
					$success++;
				}
			}

			$pageDone++;
			$nextPageId = $res['pageId'] ?? '';
			if (empty($nextPageId) || count($list) < $pageSize) {
				break;
			}
			$pageId = $nextPageId;
			usleep(300000);
		}
		} // foreach map (cid => navid)

		$extra = '';
		if (!empty($errCids)) {
			$extra = '；部分分类拉取失败：' . implode('；', $errCids);
		}
		exit(json_encode(array(
			"info" => "大淘客朋友圈采集完成（" . count($map) . " 个商品分类）：翻页 {$pageDone} 次，成功入库 {$success} 条，更新 {$update} 条" . $extra,
			"status" => "y"
		)));
	}

	/**
	 * 大淘客朋友圈采集（计划任务专用）：读取后台保存参数，定时跑无需传参。
	 * 复用 collectDtk() 的大淘客朋友圈接口采集逻辑。
	 * @return array ['ok'=>bool,'output'=>string]
	 */
	public function collectDtkCron(){
		set_time_limit(0);
		$api = \app\common\ConfigStore::load('api');
		$dtkAppKey = $api['dtk_appkey'] ?? '';
		$dtkAppSecret = $api['dtk_appsecret'] ?? '';
		if (empty($dtkAppKey) || empty($dtkAppSecret)) {
			return array('ok' => false, 'output' => '请先在后台配置大淘客 AppKey/AppSecret，方可采集大淘客朋友圈');
		}

		$tjk = new \ZhiCms\ext\Tjk(array(
			'DtkappKey'    => $dtkAppKey,
			'DtkappSecret' => $dtkAppSecret,
			'HdkApiKey'    => $api['hdk_appkey'] ?? '',
		));
		$dtk = $tjk->getDtk();
		if (!$dtk) return array('ok' => false, 'output' => '大淘客API未配置');

		// 分类映射：商品分类(cid) => 文章分类(navid)，由用户在设置项中手动指定，不做自动映射
		$map = isset($api['dtk_moments_map']) && is_array($api['dtk_moments_map']) ? $api['dtk_moments_map'] : array();
		$map = array_filter($map, function($v, $k){ return intval($k) > 0 && intval($v) > 0; }, ARRAY_FILTER_USE_BOTH);
		$sort  = isset($api['dtk_moments_sort']) ? intval($api['dtk_moments_sort']) : 0;
		$pages = isset($api['dtk_moments_pages']) ? max(1, intval($api['dtk_moments_pages'])) : 3;
		if ($sort < 0 || $sort > 6) $sort = 0;

		// 计划任务无管理员 SESSION，取默认管理员作为文章作者
		$defaultAuthor = '管理员';
		$defaultAuthorPic = '';
		$admRows = obj("api/ApiData")->dataSelect("yun_manage", array(), "id ASC");
		if (!empty($admRows) && is_array($admRows)) {
			$adm = isset($admRows[0]) ? $admRows[0] : $admRows;
			if (!empty($adm['nickname'])) $defaultAuthor = $adm['nickname'];
			if (!empty($adm['pic'])) $defaultAuthorPic = $adm['pic'];
		}

		if (empty($map)) {
			return array('ok' => false, 'output' => '未设置大淘客朋友圈分类映射（商品分类=>文章分类），请先在大淘客朋友圈采集中选择商品分类并指定文章分类后保存');
		}

		$success = 0; $update = 0; $pageDone = 0;
		// 外层：按用户映射的每个商品分类逐个拉取，文章归入对应文章分类 navid
		foreach ($map as $loopCid => $navid) {
			$pageId = '';
		for ($p = 0; $p < $pages; $p++) {
			$res = $dtk->FriendsCircleList($pageId, 50, $sort, intval($loopCid));
			if (empty($res['code']) || $res['code'] != 1) break;
			$list = $res['items'];
			if (empty($list) || !is_array($list)) break;

			foreach ($list as $item) {
				$itemid = $item['goodsId'] ?? '';
				if (empty($itemid)) continue;

				$title = trim($item['title'] ?? '');
				$dtitle = trim($item['dtitle'] ?? '');
				$mainPic = $item['mainPic'] ?? '';
				$desc = trim($item['desc'] ?? '');
				if ($desc !== '') $desc = $this->cleanMomentsText($desc);
				$circleText = $item['circleText'] ?? '';
				if ($circleText !== '') $circleText = $this->cleanMomentsText($circleText);

				$contentText = '';
				if ($desc !== '') $contentText .= '<p>' . $desc . '</p>';
				if ($circleText !== '' && trim(preg_replace('/\s+/u', '', $circleText)) !== trim(preg_replace('/\s+/u', '', $desc))) {
					$contentText .= '<p class="circle-text">' . $circleText . '</p>';
				}
				$contentText .= $this->buildGoodsMarker($dtk, $item);

				$decSource = $desc !== '' ? $desc : $circleText;
				$decText = strip_tags($decSource);
				$decText = mb_substr($decText, 0, 120, 'UTF-8');

				$kwParts = array_unique(array_filter(array($title, $dtitle, $desc)));
				$keywords = implode(' ', $kwParts);

				$data = array(
					'goodsId'  => $itemid,
					'itemLink' => $item['itemLink'] ?? ('https://item.taobao.com/item.htm?id=' . $itemid),
					'title'    => $title,
					'content'  => $contentText,
					'cid'      => 0,
					'navid'    => $navid,
					'mainPic'  => $mainPic,
					'keywords' => $keywords,
					'dec'      => $decText,
					'view'     => 0,
					'like'     => 0,
					'lock'     => 0,
					'status'   => 1,
					'couponEndTime' => '',
					'date'     => date("Y-m-d H:i:s", time()),
				);
				$data['author'] = $defaultAuthor;
				$data['author_pic'] = $defaultAuthorPic;

				if ($this->saveArticleUpsert($data, $itemid) === 'update') {
					$update++;
				} else {
					$success++;
				}
			}
			$pageDone++;
			$nextPageId = $res['pageId'] ?? '';
			if (empty($nextPageId) || count($list) < 50) break;
			$pageId = $nextPageId;
			usleep(300000);
		}
		}
		return array('ok' => true, 'output' => "大淘客朋友圈采集完成（" . count($map) . " 个商品分类）：翻页{$pageDone}次，成功入库{$success}条，更新{$update}条");
	}

	/**
	 * 清洗朋友圈发圈文案：去除 [emoji:xxx] / [口令] 等占位符，避免污染正文
	 */
	private function cleanMomentsText($text){
		if ($text === '') return '';
		// 去除形如 [emoji:xxx]、[xx:xx] 的占位符
		$text = preg_replace('/\[[^\]\n]{0,30}\]/u', '', $text);
		// 去除淘口令等不可见字符块（￥...￥ / 🍑...🍑）
		$text = preg_replace('/[￥€$][^￥€$]{4,40}[￥€$]/u', '', $text);
		$text = preg_replace('/(🍑|🍊|🔗)\s*[^🍑🍊🔗\n]{0,40}\s*(🍑|🍊|🔗)/u', '', $text);
		return trim($text);
	}

	/**
	 * 资讯采集（聚合数据 235 新闻头条 + 850 AI新闻简报）
	 * 接收两个独立 key 与分类映射（聚合分类 => 本地发现分类 navid），逐类拉取新闻入库 yun_article。
	 */
	public function newsCollect(){
        $this->checkManageSession();
        set_time_limit(0);
        header('Content-Type: application/json; charset=utf-8');

        // key 与 map 均用原始 $_POST 取值：arg() 会对值做 htmlspecialchars，
        // 破坏 JSON 引号导致 map 解析失败（表现为"成功入库 0 条"）。
        $key235 = trim(isset($_POST['key235']) ? $_POST['key235'] : (isset($_GET['key235']) ? $_GET['key235'] : ''));
        $key850 = trim(isset($_POST['key850']) ? $_POST['key850'] : (isset($_GET['key850']) ? $_GET['key850'] : ''));
        $map235Raw = isset($_POST['map235']) ? $_POST['map235'] : (isset($_GET['map235']) ? $_GET['map235'] : '');
        $map850Raw = isset($_POST['map850']) ? $_POST['map850'] : (isset($_GET['map850']) ? $_GET['map850'] : '');
        $map235 = @json_decode(html_entity_decode($map235Raw, ENT_QUOTES), true);
        $map850 = @json_decode(html_entity_decode($map850Raw, ENT_QUOTES), true);
        if (!is_array($map235)) $map235 = array();
        if (!is_array($map850)) $map850 = array();
        $pages  = max(1, intval(isset($_POST['pages']) ? $_POST['pages'] : (isset($_GET['pages']) ? $_GET['pages'] : 3)));

        if (empty($key235) && empty($key850)) {
            exit(json_encode(array("info" => "请至少填写一个聚合接口 Key（新闻头条或 AI新闻简报）", "status" => "n")));
        }
        // 若填了 key 但对应分类映射为空，给出明确提示，避免"成功入库 0 条"误导
        if (!empty($key235) && empty($map235)) {
            exit(json_encode(array("info" => "已填新闻头条 Key，但未选择任何 235 分类并指定本地发现分类，请检查弹窗中的分类映射", "status" => "n")));
        }
        if (!empty($key850) && empty($map850)) {
            exit(json_encode(array("info" => "已填 AI新闻简报 Key，但未选择任何 850 分类并指定本地发现分类，请检查弹窗中的分类映射", "status" => "n")));
        }

        // 保存 key 到后台配置（下次预填）
        $api = \app\common\ConfigStore::load('api');
        if (!is_array($api)) $api = array();
        if (!empty($key235)) $api['juhe_235_key'] = $key235;
        if (!empty($key850)) $api['juhe_850_key'] = $key850;
        // 持久化分类映射，供「计划任务-资讯采集」定时跑时使用（无需每次传 map）
        $api['juhe_235_map'] = $map235;
        $api['juhe_850_map'] = $map850;
        \app\common\ConfigStore::save('api', $api);

        $success = 0;
        $update = 0;
        $failMsg = array();

        // ---- 接口 235 新闻头条 ----
        if (!empty($key235) && !empty($map235) && is_array($map235)) {
            foreach ($map235 as $type => $navid) {
                $navid = intval($navid);
                if ($navid <= 0) continue;
                $type = preg_replace('/[^a-z0-9]/i', '', $type);
                if ($type === '') continue;
                $res = \app\common\JuheService::fetch235($key235, $type, $pages);
                if (!$res['ok']) {
                    $failMsg[] = "235[{$type}]: " . $res['error'];
                    continue;
                }
                foreach ($res['list'] as $news) {
                    if (empty($news['title'])) continue;
                    if ($this->insertNews($news, $navid, 'juhe_235') === 'update') {
                        $update++;
                    } else {
                        $success++;
                    }
                }
            }
        }

        // ---- 接口 850 AI新闻简报 ----
        if (!empty($key850) && !empty($map850) && is_array($map850)) {
            foreach ($map850 as $type => $navid) {
                $navid = intval($navid);
                if ($navid <= 0) continue;
                $type = preg_replace('/[^a-z0-9]/i', '', $type);
                if ($type === '') continue;
                $res = \app\common\JuheService::fetch850($key850, $type, $pages);
                if (!$res['ok']) {
                    $failMsg[] = "850[{$type}]: " . $res['error'];
                    continue;
                }
                foreach ($res['list'] as $news) {
                    if (empty($news['title'])) continue;
                    if ($this->insertNews($news, $navid, 'juhe_850') === 'update') {
                        $update++;
                    } else {
                        $success++;
                    }
                }
            }
        }

        if (!empty($failMsg)) {
            exit(json_encode(array(
                "info" => "采集完成：成功入库 {$success} 条，更新 {$update} 条。部分分类失败：" . implode('；', $failMsg),
                "status" => $success > 0 ? "y" : "n"
            )));
        }

        exit(json_encode(array(
            "info" => "资讯采集完成：成功入库 {$success} 条，更新 {$update} 条",
            "status" => "y"
        )));
	}

	/**
	 * 资讯采集（计划任务专用）：读取后台已保存的聚合接口 Key 与分类映射，定时跑无需传参。
	 * @return array ['ok'=>bool,'output'=>string]
	 */
	public function newsCollectCron(){
		@set_time_limit(0);
		$api = \app\common\ConfigStore::load('api');
		$key235 = isset($api['juhe_235_key']) ? trim($api['juhe_235_key']) : '';
		$key850 = isset($api['juhe_850_key']) ? trim($api['juhe_850_key']) : '';
		$map235 = (isset($api['juhe_235_map']) && is_array($api['juhe_235_map'])) ? $api['juhe_235_map'] : array();
		$map850 = (isset($api['juhe_850_map']) && is_array($api['juhe_850_map'])) ? $api['juhe_850_map'] : array();
		$pages  = 3;

		if (empty($key235) && empty($key850)) {
			return array('ok' => false, 'output' => '尚未配置聚合接口 Key（后台「发现-资讯采集」或 API 设置中填写）');
		}
		// 分别校验：已填 Key 但对应分类映射为空时，明确提示，避免静默采集 0 条。
		// 注意用「各自独立判断」而非整体 AND：例如只配了 235 Key 却漏填 map235，
		// 旧逻辑 && 条件不成立会直接跳过、静默 0 条，不符合"配置正确即能采"的预期。
		$missMap = array();
		if (!empty($key235) && empty($map235)) {
			$missMap[] = '新闻头条(235)';
		}
		if (!empty($key850) && empty($map850)) {
			$missMap[] = 'AI新闻简报(850)';
		}
		if (!empty($missMap)) {
			return array('ok' => false, 'output' => '已配置 Key 但未设置分类映射，无法采集：' . implode('、', $missMap) . ' 请在后台资讯采集弹窗中指定本地发现分类');
		}

		$success = 0; $update = 0; $failMsg = array();
		if (!empty($key235) && !empty($map235)) {
			foreach ($map235 as $type => $navid) {
				$navid = intval($navid);
				if ($navid <= 0) continue;
				$type = preg_replace('/[^a-z0-9]/i', '', $type);
				if ($type === '') continue;
				$res = \app\common\JuheService::fetch235($key235, $type, $pages);
				if (!$res['ok']) { $failMsg[] = "235[{$type}]: " . $res['error']; continue; }
				foreach ($res['list'] as $news) {
					if (empty($news['title'])) continue;
					if ($this->insertNews($news, $navid, 'juhe_235') === 'update') { $update++; } else { $success++; }
				}
			}
		}
		if (!empty($key850) && !empty($map850)) {
			foreach ($map850 as $type => $navid) {
				$navid = intval($navid);
				if ($navid <= 0) continue;
				$type = preg_replace('/[^a-z0-9]/i', '', $type);
				if ($type === '') continue;
				$res = \app\common\JuheService::fetch850($key850, $type, $pages);
				if (!$res['ok']) { $failMsg[] = "850[{$type}]: " . $res['error']; continue; }
				foreach ($res['list'] as $news) {
					if (empty($news['title'])) continue;
					if ($this->insertNews($news, $navid, 'juhe_850') === 'update') { $update++; } else { $success++; }
				}
			}
		}
		$msg = "资讯采集完成：成功入库 {$success} 条，更新 {$update} 条";
		if (!empty($failMsg)) $msg .= '；部分分类失败：' . implode('；', $failMsg);
		return array('ok' => true, 'output' => $msg);
	}

	/**
	 * 将一条标准化新闻写入 yun_article（归到本地发现分类 navid）。
	 * 改为 upsert 语义：按 surl（聚合接口唯一键）/ title 判断，已存在则更新，避免重复丢弃。
	 * @return string 'insert' | 'update'
	 */
	private function insertNews($news, $navid, $source){
        $content = '';
        if (!empty($news['content'])) {
            $content .= '<p>' . $news['content'] . '</p>';
        }
        if (!empty($news['url'])) {
            $content .= '<p><a href="' . htmlspecialchars($news['url'], ENT_QUOTES) . '" target="_blank" rel="nofollow">查看原文</a></p>';
        }
        $dec = strip_tags($news['summary'] ?: $news['content']);
        $dec = mb_substr($dec, 0, 120, 'UTF-8');

        $data = array(
            'goodsId'       => null,
            'itemLink'      => null,
            'cid'           => 0,
            'navid'         => $navid,
            'title'         => $news['title'],
            'content'       => $content,
            'mainPic'       => $news['pic'] ?? '',
            'keywords'      => $news['title'],
            'dec'           => $dec,
            'author'        => $news['source'] ?? '',
            'author_pic'    => $this->resolveDefaultAuthorPic(),
            'laiyuan'       => $source,
            'surl'          => $news['uniquekey'] ?? '',
            'sort'          => 0,
            'hits'          => 0,
            'bili'          => 0,
            'sheng'         => '',
            'allow_comment' => 1,
            'featured'      => 0,
            'view'          => 0,
            'like'          => 0,
            'lock'          => 0,
            'status'        => 1,
            'couponEndTime' => '',
            'date'          => $news['pubDate'] ?? date('Y-m-d H:i:s'),
        );

        $api = obj("api/ApiData");
        // 去重键：优先 surl（聚合唯一键），其次 title
        $existCond = null;
        if (!empty($news['uniquekey'])) {
            $chk = $api->dataCount("yun_article", array("surl" => $news['uniquekey']));
            if ($chk > 0) $existCond = array("surl" => $news['uniquekey']);
        }
        if ($existCond === null && !empty($news['title'])) {
            $chk = $api->dataCount("yun_article", array("title" => $news['title']));
            if ($chk > 0) $existCond = array("title" => $news['title']);
        }
        if ($existCond !== null) {
            // 已存在：更新内容/图片/摘要等，保留原 id 与统计字段（view/like/hits）
            $api->dataUpdate("yun_article", $data, $existCond);
            return 'update';
        }
        $api->insertData("yun_article", $data);
        return 'insert';
	}

	/**
	 * 朋友圈商品文章 upsert：按 goodsId 去重，已存在则更新整条记录（含 content/图片/标题），不存在则新增。
	 * @param array $data 完整字段
	 * @param string $itemid 商品 ID（goodsId）
	 * @return string 'insert' | 'update'
	 */
	private function saveArticleUpsert($data, $itemid){
		$api = obj("api/ApiData");
		$chk = $api->dataCount("yun_article", array("goodsId" => $itemid));
		if ($chk > 0) {
			// 更新时保留原统计字段（浏览/点赞），仅刷新内容类字段
			$api->dataUpdate("yun_article", $data, array("goodsId" => $itemid));
			return 'update';
		}
		$api->insertData("yun_article", $data);
		return 'insert';
	}

	/**
	 * 解析文章作者头像：后台手动操作取当前管理员 SESSION；计划任务/定时无 SESSION 时取默认管理员（第一条）
	 */
	private function resolveDefaultAuthorPic(){
		if (!empty($_SESSION['manage_pic'])) {
			return $_SESSION['manage_pic'];
		}
		$admRows = obj("api/ApiData")->dataSelect("yun_manage", array(), "id ASC");
		if (!empty($admRows) && is_array($admRows)) {
			$adm = isset($admRows[0]) ? $admRows[0] : $admRows;
			if (!empty($adm['pic'])) return $adm['pic'];
		}
		return '';
	}

	/**
	 * 生成站内商品卡片标记 [ZhiCmsUrl]短链接[/ZhiCmsUrl]
	 * 1. itemid 调用大淘客转链接口 GetPrivilegeLink 获取短链（shortUrl）
	 * 2. 前端 findItems 通过 Tjk 本地接口（Dtk::ParseContent + GetGoodsDetails）解析短链并渲染卡片
	 * 3. 卡片含：标题/券后价/原价/销量/优惠券金额/图片/「立即去看看」按钮
	 * 兜底：Dtk 未配置或短链为空时，用商品详情页链接；解析失败时渲染去购买按钮
	 */
	private function buildGoodsMarker($dtk, $item) {
        // 好单库朋友圈素材均为淘宝联盟商品，平台固定 tb。
        // itemid 取值需兼容多种字段名：原始好单库素材用 itemid；
        // 经 standardizeItem 归一化后（collectCron/单条采集均走此路径）字段名为 goodsId。
        // 旧逻辑仅取 itemid，导致标准化后的 $item 取不到 → 直接 return '' → 商品卡标记丢失。
        $itemid = $item['items']['itemid'] ?? ($item['itemid'] ?? ($item['goodsId'] ?? ''));
        if (empty($itemid)) {
            return '';
        }

        // 平台归一：优先取 item 真实来源，默认 tb（朋友圈素材多淘宝）
        $platform = 'tb';
        if (!empty($item['item_from'])) {
            $pf = strtolower(trim($item['item_from']));
            $pfMap = array('taobao' => 'tb', 'tb' => 'tb', 'dtk' => 'tb',
                           'jd' => 'jd', 'jingdong' => 'jd',
                           'pdd' => 'pdd', 'paioduoduo' => 'pdd',
                           'vip' => 'vip', 'weipinhui' => 'vip');
            $platform = $pfMap[$pf] ?? 'tb';
        }

        // 统一走站内转链（buy-<platform>.html?id= 伪静态），便于统计与佣金追踪
        $goodsUrl = url('index/redirect/jump', array('platform' => $platform, 'id' => $itemid));

        return "\n[ZhiCmsUrl]" . $goodsUrl . "[/ZhiCmsUrl]\n";
	}

	public function edit(){


		$this->editArticle();
	}

	public function addArticle(){


	$this->checkManageSession();

	    
	    $Siteinfo = \app\common\ConfigStore::load('site');
	    $newData= new \ZhiCms\ext\Weixin;
		if(!\IS_POST){
			$this->pageText=array("发现管理","发布文章");
           $this->categories = $this->getGoodsCategories();
           $this->navs = $this->getFindNavs();
           // 当前管理员信息（发布文章默认调用的作者昵称与头像）
           $this->adminNick = isset($_SESSION['manage_nickname']) && $_SESSION['manage_nickname'] !== '' ? $_SESSION['manage_nickname'] : (isset($_SESSION['manage_system']) ? $_SESSION['manage_system'] : '管理员');
           $this->adminPic = isset($_SESSION['manage_pic']) ? $_SESSION['manage_pic'] : '';
           $this->ret = array();
           $lock=$this->arg("lock");
           $goodsId=$this->arg("goodsid");
           if($goodsId!=''){
              $host=$Siteinfo['apiurl']."?s=App.taobao.friends";
              $arr=array ( 
              'goodsid' => $goodsId, 
              );
	    $rootUrl = $host . '&' . http_build_query($arr);
		$data=obj("api/Api")->objectArray(json_decode($newData->http($rootUrl)));
    	$this->ret=$data['data']['list'][0];
    	$this->html='<input type="hidden" name="goodsid" value="'.$goodsId.'" />';
           }
    		

 
    		$this->display();
			exit;
		}else{
    		// 清理可能的编辑器额外字段
    		unset($_POST['editor-md-container-html-code']);
    		unset($_POST['editor-md-container-article-html-code']);

    		 
    		// TP 风格表单校验（validator 借鉴 think\Validate 设计，零依赖可复用）
    		$validator = \app\common\Validator::make(
    			array(
    				'title' => 'require|max:120',
    				'navid' => 'number',
    				'cid'   => 'number',
    				'sort'  => 'integer',
    				'hits'  => 'integer',
    			),
    			array(
    				'title.require' => '文章标题不能为空',
    				'title.max'     => '文章标题不能超过120个字符',
    				'navid.number'  => '分类ID必须为数字',
    				'cid.number'    => '栏目ID必须为数字',
    			),
    			array('title' => '标题', 'navid' => '分类', 'cid' => '栏目')
    		);
    		if (!$validator->check($_POST)) {
    			$this->redirect(url('manage/find/addarticle') . '?error=' . urlencode($validator->getError()));
    			return;
    		}

    		$goodsId=$this->arg("goodsid");
    		$title=$this->arg("title");
    		$cid=intval($this->arg("cid"));
    		$navid=intval($this->arg("navid"));
    		$pic=$this->arg("pic");
    		$keywords=$this->arg("keywords");
    		$dec=$this->arg("dec");
    		// 富文本正文：注意 Controller::arg() 内部会用 preg_replace("@<(.*?)>@is","",$args)
    		// 把所有 HTML 标签（含 <img>）整段删除，导致编辑器上传的图片无法入库。
    		// 因此正文必须直接从 $_POST['body'] 取原始值，只剥离危险的 <script>/<iframe>/<style>，
    		// 保留编辑器工具栏生成的 <img>/<a>/<p> 等标签，避免图片丢失。
    		$rawBody = isset($_POST['body']) ? $_POST['body'] : '';
    		$rawBody = preg_replace("@<script(.*?)</script>@is", "", $rawBody);
    		$rawBody = preg_replace("@<iframe(.*?)</iframe>@is", "", $rawBody);
    		$rawBody = preg_replace("@<style(.*?)</style>@is", "", $rawBody);
		// 清除正文中残留的模板标签字面量（如 {$ret['content']|raw}），避免前台原样显示
		$content = $this->cleanTemplateTags($rawBody);
    		$status=$this->arg("status", 0);
    		$author=$this->arg("author", '');
    		$laiyuan=$this->arg("laiyuan", '');
    		$surl=$this->arg("surl", '');
    		$sort=intval($this->arg("sort", 0));
    		$hits=intval($this->arg("hits", 0));
    		$bili=intval($this->arg("bili", 0));
    		$sheng=$this->arg("sheng", '');
    		$allow_comment=$this->arg("allow_comment", 1);
    		$featured=$this->arg("featured", 0);

		// surl 为文章来源链接，可能含长追踪参数，超出字段长度会报 1406，
		// 取表单值后截断到与数据库字段一致的最大长度（varchar(1000)）
		$surl = mb_substr(trim($surl), 0, 1000, 'UTF-8');

		// AI 自动生成关键词和描述（未填写时）
		if (empty($keywords)) {
			$keywords = \app\common\AiService::extractKeywords($title, $content);
		}
		if (empty($dec)) {
			$dec = \app\common\AiService::generateDec($title, $content);
		}

		// AI 自动匹配商品（无 goodsId 且内容不含 ZhiCmsUrl 卡片时）
		$itemLink = '';
		if (empty($goodsId) && strpos($content, '[ZhiCmsUrl]') === false) {
			$matchedGoods = $this->aiMatchAndBuildGoods($title, $content);
			if (!empty($matchedGoods)) {
				if (!empty($matchedGoods['goodsId'])) {
					$goodsId = $matchedGoods['goodsId'];
				}
				if (!empty($matchedGoods['marker'])) {
					$content .= $matchedGoods['marker'];
				}
				if (!empty($matchedGoods['itemLink'])) {
					$itemLink = $matchedGoods['itemLink'];
				}
				if (!empty($matchedGoods['pic']) && empty($pic)) {
					$pic = $matchedGoods['pic'];
				}
				}
				}

				// 封面图兜底：用户未上传封面($pic 为空) 且 AI 也未匹配到商品图时，
				// 从正文里提取第一张 <img> 作为封面图，避免前台封面空白。
				if (empty($pic)) {
				if (preg_match('/<img[^>]+src\s*=\s*["\']([^"\']+)["\']/i', $content, $m)) {
				$pic = $m[1];
				}
				}

    		 $startTime=strtotime(date("Y-m-d",time()));
             $endTime=$startTime+60*60*24*7;
    		 $data['goodsId']=null;
    		 $data['itemLink']=null;
    		 $data['cid']=$cid;
    		 $data['navid']=$navid;
    		 $data['couponEndTime']=date("Y-m-d H:i:s",$endTime);
    		if($goodsId!='' && $goodsId!=null){
    		 $newData= new \ZhiCms\ext\Weixin;
              $host=$Siteinfo['apiurl']."?s=App.taobao.friends";
              $arr=array ( 
              'goodsid' => $goodsId, 
              );
	          $rootUrl = $host . '&' . http_build_query($arr);
		      $dataSer=obj("api/Api")->objectArray(json_decode($newData->http($rootUrl)));
    	      $datas=$dataSer['data']['list'][0];
    	      $data['goodsId']=$goodsId;
    		  $data['itemLink']=$datas['itemLink'];
    		  $data['cid']=$datas['cid'];
    		  $data['couponEndTime']=$datas['couponEndTime'];
    		} elseif (!empty($itemLink)) {
    			$data['itemLink'] = $itemLink;
    		}
    		
  
           
    	     
    		 $data['title']=$title;
    		 $data['lock']=0;
    		 $data['status']=$status ? 1 : 0;
    		 // 作者默认调用当前管理员昵称（前台文章展示用）
    		 if(trim($author) === ''){
    		     $author = isset($_SESSION['manage_nickname']) && $_SESSION['manage_nickname'] !== '' ? $_SESSION['manage_nickname'] : (isset($_SESSION['manage_system']) ? $_SESSION['manage_system'] : '管理员');
    		 }
    		 $data['author']=$author;
    		 $data['author_pic']=isset($_SESSION['manage_pic']) ? $_SESSION['manage_pic'] : '';
    		 $data['laiyuan']=$laiyuan;
    		 $data['surl']=$surl;
    		 $data['sort']=$sort;
    		 $data['hits']=$hits;
    		 $data['bili']=$bili;
    		 $data['sheng']=$sheng;
    		 $data['allow_comment']=$allow_comment ? 1 : 0;
    		 $data['featured']=$featured ? 1 : 0;
    		 $data['mainPic']=$pic;
    		 $data['keywords']=$keywords;
    		 $data['dec']=$dec;
    		 $data['content']=$content;
    		 $data['view']=0;
			 $data['like']=0;
			 $data['date']=date("Y-m-d H:i:s",time());
			 
		     obj("api/ApiData")->insertData("yun_article",$data);
			 $url="index.php?r=manage/find/index";
			 $this->redirect($url, $code = 302);
		}
		
	}

	public function editArticle(){



	$this->checkManageSession();

    $Siteinfo = \app\common\ConfigStore::load('site');
    $newData= new \ZhiCms\ext\Weixin;
	if(!\IS_POST){
    		$this->pageText=array("发现管理","编辑文章");
  


            $id=intval($this->arg("id"));
            $where['id'] = $id;
            $ret=obj("api/ApiData")->dataSelect("yun_article",$where);
            $this->ret=$ret;
            $this->categories = $this->getGoodsCategories();
            $this->navs = $this->getFindNavs();
            $this->adminNick = isset($_SESSION['manage_nickname']) && $_SESSION['manage_nickname'] !== '' ? $_SESSION['manage_nickname'] : (isset($_SESSION['manage_system']) ? $_SESSION['manage_system'] : '管理员');
            $this->adminPic = isset($_SESSION['manage_pic']) ? $_SESSION['manage_pic'] : '';
            $this->html='<input type="hidden" name="id" value="'.$ret['id'].'" /><input type="hidden" name="goodsid" value="'.$ret['goodsId'].'" />';
			$this->display('app/manage/view/find/addarticle');
			exit;
		}else{
			// 清理可能的编辑器额外字段
			unset($_POST['editor-md-container-html-code']);
			unset($_POST['editor-md-container-article-html-code']);

			 $id=intval($this->arg("id"));
    		// TP 风格表单校验
    		$validator = \app\common\Validator::make(
    			array(
    				'title' => 'require|max:120',
    				'navid' => 'number',
    				'cid'   => 'number',
    				'id'    => 'require|number',
    			),
    			array(
    				'title.require' => '文章标题不能为空',
    				'id.require'    => '文章ID缺失',
    				'navid.number'  => '分类ID必须为数字',
    				'cid.number'    => '栏目ID必须为数字',
    			),
    			array('title' => '标题', 'navid' => '分类', 'cid' => '栏目', 'id' => 'ID')
    		);
    		if (!$validator->check($_POST)) {
    			$this->redirect(url('manage/find/editorarticle', array('id' => $id)) . '?error=' . urlencode($validator->getError()));
    			return;
    		}
    		$title=$this->arg("title");
    		$pic=$this->arg("pic");
    		$cid=intval($this->arg("cid"));
    		$navid=intval($this->arg("navid"));
    		$keywords=$this->arg("keywords");
    		$dec=$this->arg("dec");
    		// 富文本正文：同 addArticle，直接从 $_POST['body'] 取原始值，
    		// 绕过 arg() 的 preg_replace 标签删除（会删掉 <img> 导致图片丢失），
    		// 仅剥离危险标签并清除模板字面量，保留编辑器工具栏生成的标签。
    		$rawBody = isset($_POST['body']) ? $_POST['body'] : '';
    		$rawBody = preg_replace("@<script(.*?)</script>@is", "", $rawBody);
    		$rawBody = preg_replace("@<iframe(.*?)</iframe>@is", "", $rawBody);
    		$rawBody = preg_replace("@<style(.*?)</style>@is", "", $rawBody);
		// 清除正文中残留的模板标签字面量（如 {$ret['content']|raw}），避免前台原样显示
		$content = $this->cleanTemplateTags($rawBody);
    		$status=$this->arg("status", 0);
    		$author=$this->arg("author", '');
    		$laiyuan=$this->arg("laiyuan", '');
    		$surl=$this->arg("surl", '');
    		$sort=intval($this->arg("sort", 0));
    		$hits=intval($this->arg("hits", 0));
    		$bili=intval($this->arg("bili", 0));
    		$sheng=$this->arg("sheng", '');
    		$allow_comment=$this->arg("allow_comment", 1);
    		$featured=$this->arg("featured", 0);

		// surl 为文章来源链接，含长追踪参数时可能超长，截断到与字段一致（varchar(1000)）
		$surl = mb_substr(trim($surl), 0, 1000, 'UTF-8');


      		$data['title']=$title;
    		 $data['status']=$status ? 1 : 0;
    		 // 作者默认调用当前管理员昵称（前台文章展示用）
    		 if(trim($author) === ''){
    		     $author = isset($_SESSION['manage_nickname']) && $_SESSION['manage_nickname'] !== '' ? $_SESSION['manage_nickname'] : (isset($_SESSION['manage_system']) ? $_SESSION['manage_system'] : '管理员');
    		 }
    		 $data['author']=$author;
    		 $data['author_pic']=isset($_SESSION['manage_pic']) ? $_SESSION['manage_pic'] : '';
    		 $data['laiyuan']=$laiyuan;
    		 $data['surl']=$surl;
    		 $data['sort']=$sort;
    		 $data['hits']=$hits;
    		 $data['bili']=$bili;
    		 $data['sheng']=$sheng;
    		 $data['allow_comment']=$allow_comment ? 1 : 0;
    		 $data['featured']=$featured ? 1 : 0;
    		 $data['mainPic']=$pic;
    		 $data['keywords']=$keywords;
    		 $data['dec']=$dec;
    		 $data['content']=$content;

    		  $data['cid']=$cid;
    		  $data['navid']=$navid;

             $where['id'] = $id;
             obj("api/ApiData")->dataUpdate("yun_article",$data,$where);
             $url="index.php?r=manage/find/index";
			 $this->redirect($url, $code = 302);
			
		}

	}

	public function batch(){

	$this->checkManageSession();
		error_reporting(0);
		header('Content-Type: application/json; charset=utf-8');

		$action = $this->arg("action");
		$ids    = $this->arg("ids");

		if(empty($ids)){
			exit(json_encode(array("info" => "请选择要操作的文章", "status" => "n")));
		}

		// 安全过滤，确保 ID 都是整数
		$idArr = array_map('intval', explode(',', $ids));
		$idArr = array_filter($idArr, function($v){ return $v > 0; });
		if(empty($idArr)){
			exit(json_encode(array("info" => "无效的文章ID", "status" => "n")));
		}
		$idStr = implode(',', $idArr);
		$count = count($idArr);

		// where() 方法仅接受数组，不能用字符串（会触发 TypeError）
		$where = array("`id` IN ({$idStr})");

		switch($action){
			case 'publish':
				obj('api/ApiData')->dataUpdate('yun_article', array('status' => 1), $where);
				exit(json_encode(array("info" => "已批量发布 {$count} 篇文章", "status" => "y")));

			case 'unpublish':
				obj('api/ApiData')->dataUpdate('yun_article', array('status' => 0), $where);
				exit(json_encode(array("info" => "已批量取消发布 {$count} 篇文章", "status" => "y")));

			case 'delete':
				$_m = obj('api/ApiData');
				$_m->table($_m->realTable('yun_article'), true)->where($where)->delete();
				exit(json_encode(array("info" => "已批量删除 {$count} 篇文章", "status" => "y")));

			default:
				exit(json_encode(array("info" => "未知操作类型", "status" => "n")));
		}
	}

	public function delete(){




	$this->checkManageSession();

		error_reporting(0);
		$id=intval($this->arg("id"));

		$where = "`id` = ?";
        obj('api/ApiData')->deleteThis('yun_article', $where, array($id));

 
        exit(json_encode(array("info" => "删除成功", "status" => "y")));
	} 


	
	/**
	 * 删除文章（视图 find/index.html 调用 manage/find/del）
	 * 与 delete() 等价，提供 del 入口以匹配前端路由
	 */
	public function del(){
		return $this->delete();
	}

public function type(){
 


	$this->checkManageSession();

		$this->pageText=array("分类管理","管理发现分类");
		$where[] = "1";
    	$baseUrl = "index.php?r=manage/find/type";
        $page = obj('api/ApiData')->page("50", "yun_nav", $where, "`id` DESC", $baseUrl);
        $this->page = $page;
		$this->display();
	}

	public function addType(){


	$this->checkManageSession();


		if(!\IS_POST){
			$this->pageText=array("分类管理","新建分类");
			$this->color=self::diyColor();
			$this->display();
			exit;
		}else{
			self::checkTypeForm();
			try {
				$data = obj('api/Api')->Form($this->POSTarg());
				// 参考 ad/addHuan：px 为 int 列，空字符串插入会触发 MySQL 严格模式报错，
				// 此处统一转为 int 并给默认值，避免 "请求异常"（返回 HTML 错误页）。
				$data['px'] = isset($data['px']) && $data['px'] !== '' ? intval($data['px']) : 0;
				$newId = obj('api/ApiData')->insertData('yun_nav', $data);
				if (!$newId) {
					exit(json_encode(array("info" => "保存失败，请重试", "status" => "n")));
				}
				exit(json_encode(array("info" => "保存成功", "status" => "y", "id" => $newId)));
			} catch (\Throwable $e) {
				exit(json_encode(array("info" => "保存失败：" . $e->getMessage(), "status" => "n")));
			}
		}
	}

	public function editType(){




	$this->checkManageSession();

     if(!\IS_POST){
			$this->pageText=array("分类管理","编辑分类");
			$id=intval($this->arg("id"));
            $where['id'] = $id;
            $ret=obj("api/ApiData")->dataSelect("yun_nav",$where);
            $this->ret=$ret;
            $this->html='<input type="hidden" name="id" value="'.$ret['id'].'" />';
            $this->color=self::diyColor();
			$this->display('app/manage/view/find/addtype');
			exit;
		}else{
			self::checkTypeForm();
			$id = intval($this->arg("id"));
			if ($id <= 0) {
				exit(json_encode(array("info" => "缺少有效的分类 ID", "status" => "n")));
			}
			$where['id'] = $id;
			try {
				$data = obj('api/Api')->Form($this->POSTarg());
				// 移除 data 中的 id，避免与 WHERE 的 id 重复（SET 与 WHERE 共用 :__id 占位符）
				unset($data['id']);
				// px 为 int 列，空值转 0 避免 MySQL 严格模式报错
				$data['px'] = isset($data['px']) && $data['px'] !== '' ? intval($data['px']) : 0;
				obj("api/ApiData")->dataUpdate("yun_nav", $data, $where);
				exit(json_encode(array("info" => "保存成功", "status" => "y")));
			} catch (\Throwable $e) {
				exit(json_encode(array("info" => "保存失败：" . $e->getMessage(), "status" => "n")));
			}
		}

	}

	public function deleteType(){





	$this->checkManageSession();



		$id=intval($this->arg("id"));
		$where['navid'] = $id;
		$count = obj("api/ApiData")->dataCount("yun_article", $where);

		if($count>0){
			exit(json_encode(array("info" => "请先删除改分类下的文章", "status" => "n")));
		}

		$where = "`id` = ?";
        obj('api/ApiData')->deleteThis('yun_nav', $where, array($id));
        exit(json_encode(array("info" => "删除成功", "status" => "y")));




	}

	public function navCount(){





	$this->checkManageSession();

       
            $id=intval($this->arg("id"));
            $where['navid'] = $id;
            $count = obj("api/ApiData")->dataCount("yun_article", $where);
            echo $count;
        
    }


	public function checkTypeForm(){



	$this->checkManageSession();


		if(!$this->arg("name")){
			exit(json_encode(array("info" => "请填写名称", "status" => "n")));
		}
		if(!$this->arg("pic")){
			exit(json_encode(array("info" => "请上传图标", "status" => "n")));
		}
		if(!$this->arg("keywords")){
			exit(json_encode(array("info" => "请输入关键字", "status" => "n")));
		}
		if(!$this->arg("dec")){
			exit(json_encode(array("info" => "请输入描述", "status" => "n")));
		}

	}

	/**
	 * 读取发现分类（yun_nav）列表，返回 id => name 映射，供发布/编辑文章选择
	 */
	protected function getFindNavs(){
		$list = obj("api/ApiData")->dataSelect("yun_nav", array("1"), "`px` ASC, `id` ASC");
		$map = array();
		if(!empty($list)){
			foreach($list as $row){
				$map[$row['id']] = $row['name'];
			}
		}
		return $map;
	}

	public function diyColor(){

	

	$this->checkManageSession();


		$array=array("#ac725e","#d06b64","#f83a22","#fa573c","#ff7537","#ffad46","#42d692","#16a765","#7bd148","#b3dc6c","#fbe983","#fad165","#92e1c0","#9fe1e7","#9fc6e7","#4986e7","#9a9cff","#c2c2c2","#cabdbf","#cca6ac","#f691b2","#cd74e6","#a47ae2","#555","#4600e4");
		return $array;
	}

	/**
	 * AI 匹配商品并构建卡片标记
	 * @param string $title 文章标题
	 * @param string $content 文章内容
	 * @return array
	 */
	/**
	 * 清洗正文中残留的模板标签字面量（如 {$ret['content']|raw}、{if}、{foreach}、{/if} 等）。
	 * 这些标签若被当作正文存库，前台用 {$content} 输出时会原样显示为可见文本。
	 * 仅清除典型的模板语法（{$var}、{if/foreach/for/while}、{/xx}、{* *}），
	 * 普通大括号文字（如 {示例}）不受影响。
	 */
	private function cleanTemplateTags($html){
		if (!is_string($html) || $html === '') return $html;
		// {$var}、{$var|modifier}、{$arr['k']}
		$html = preg_replace('/\{\s*\$[^}]*\}/', '', $html);
		// {if ...}、{foreach ...}、{for ...}、{while ...}、{else}、{elseif ...}
		$html = preg_replace('/\{\s*\/?\s*(if|elseif|else|foreach|for|while|switch|case|break|continue|php|literal|section|include|assign)\b[^}]*\}/i', '', $html);
		// 闭合标签 {/if}、{/foreach} 等
		$html = preg_replace('/\{\s*\/[a-zA-Z_]+\s*\}/', '', $html);
		// 注释 {* ... *} 及配对标签 {tag}{/tag} 的残留
		$html = preg_replace('/\{\*.*?\*\}/s', '', $html);
		return $html;
	}

	private function aiMatchAndBuildGoods($title, $content)
	{
		// 跨平台匹配：依次尝试 淘宝/拼多多/京东/唯品会，取第一个有结果的平台。
		// 原先写死 'taobao'，导致非淘宝商品或淘宝(大淘客)未配置时永远无结果。
		$platforms = array('taobao', 'pdd', 'jd', 'vip');
		$plat = '';
		$result = null;
		foreach ($platforms as $plat) {
			$ret = \app\common\AiService::matchGoodsByAi($title, $content, $plat);
			if ($ret['code'] == 0 && !empty($ret['items'])) {
				$result = $ret;
				break;
			}
		}

		if (empty($result) || empty($result['items'])) {
			return array();
		}

		$item = $result['items'][0];
		// 兼容各平台字段：淘宝用 itemId/itemUrl，拼多多用 goodsId/goods_sign(无url)，京东/唯品会用好单库字段
		$itemId  = isset($item['itemId'])   ? $item['itemId']   : (isset($item['goodsId']) ? $item['goodsId'] : (isset($item['itemid']) ? $item['itemid'] : ''));
		$itemUrl = isset($item['itemUrl'])  ? $item['itemUrl']  : (isset($item['url']) ? $item['url'] : (isset($item['itemurl']) ? $item['itemurl'] : ''));
		$pic     = isset($item['pic'])      ? $item['pic']      : (isset($item['itemPic']) ? $item['itemPic'] : (isset($item['pict_url']) ? $item['pict_url'] : ''));

		if (empty($itemId)) {
			return array();
		}

		// 平台归一：AI 匹配时已确定平台（$plat，如 taobao/pdd/jd/vip），
		// taobao/dtk/tmall/tm 统一规范为 tb，保证生成的站内转链形态正确、可被 findItems 解析为商品卡片。
		$linkPlat = strtolower(trim($plat));
		if (in_array($linkPlat, ['taobao', 'dtk', 'tmall', 'tm'], true)) {
			$linkPlat = 'tb';
		}
		// 优先生成站内转链（buy-<platform>.html?id=），便于统计与佣金追踪；
		// 非淘宝平台无卡片解析时也能走跳转兜底。原始平台 URL 仅作为 itemLink 兜底保留。
		$markerUrl = url('index/redirect/jump', array('platform' => $linkPlat, 'id' => $itemId));
		$marker = "\n[ZhiCmsUrl]" . $markerUrl . "[/ZhiCmsUrl]\n";

		return array(
			'goodsId'  => $itemId,
			'itemLink' => $markerUrl,
			'marker'   => $marker,
			'pic'      => $pic,
		);
	}
}