<?php
error_reporting(0); ini_set('display_errors','0');
if(!function_exists('mb_substr')){ function mb_substr($s,$st,$l=null,$e=null){ return $l===null?substr($s,$st):substr($s,$st,$l); } }
function hk($s){static $m=null;if($m===null){$m=['于'=>'於', '亏'=>'虧', '亲'=>'親', '仅'=>'僅', '从'=>'從', '仓'=>'倉', '来'=>'來', '优'=>'優', '会'=>'會', '传'=>'傳', '伞'=>'傘', '伟'=>'偉', '伤'=>'傷', '伦'=>'倫', '伪'=>'偽', '体'=>'體', '余'=>'餘', '佣'=>'傭', '侨'=>'僑', '侥'=>'僥', '俭'=>'儉', '俩'=>'倆', '兽'=>'獸', '凭'=>'憑', '凯'=>'凱', '击'=>'擊', '凿'=>'鑿', '划'=>'劃', '刘'=>'劉', '则'=>'則', '刚'=>'剛', '创'=>'創', '删'=>'刪', '别'=>'別', '刽'=>'劊', '剂'=>'劑', '动'=>'動', '务'=>'務', '医'=>'醫', '华'=>'華', '万'=>'萬', '为'=>'為', '乌'=>'烏', '无'=>'無', '时'=>'時', '实'=>'實', '宠'=>'寵', '审'=>'審', '宪'=>'憲', '宫'=>'宮', '宽'=>'寬', '宾'=>'賓', '寿'=>'壽', '将'=>'將', '专'=>'專', '寻'=>'尋', '对'=>'對', '导'=>'導', '尔'=>'爾', '尘'=>'塵', '尽'=>'盡', '层'=>'層', '属'=>'屬', '岁'=>'歲', '岛'=>'島', '岂'=>'豈', '岗'=>'崗', '币'=>'幣', '帮'=>'幫', '怀'=>'懷', '态'=>'態', '怜'=>'憐', '总'=>'總', '恋'=>'戀', '恒'=>'恆', '悬'=>'懸', '悦'=>'悅', '惊'=>'驚', '惮'=>'憚', '惯'=>'慣', '应'=>'應', '战'=>'戰', '戏'=>'戲', '户'=>'戶', '护'=>'護', '报'=>'報', '担'=>'擔', '拟'=>'擬', '拢'=>'攏', '拥'=>'擁', '拦'=>'攔', '拣'=>'揀', '择'=>'擇', '扫'=>'掃', '扬'=>'揚', '换'=>'換', '据'=>'據', '摆'=>'擺', '闭'=>'閉', '问'=>'問', '闯'=>'闖', '间'=>'間', '闲'=>'閒', '闷'=>'悶', '阅'=>'閱', '阐'=>'闡', '阔'=>'闊', '队'=>'隊', '阳'=>'陽', '阴'=>'陰', '阶'=>'階', '际'=>'際', '陆'=>'陸', '陈'=>'陳', '阵'=>'陣', '随'=>'隨', '险'=>'險', '隐'=>'隱', '隶'=>'隸', '难'=>'難', '雾'=>'霧', '静'=>'靜', '靓'=>'靚', '页'=>'頁', '顿'=>'頓', '颁'=>'頒', '颇'=>'頗', '颜'=>'顏', '顾'=>'顧', '风'=>'風', '飘'=>'飄', '飞'=>'飛', '饮'=>'飲', '饰'=>'飾', '饱'=>'飽', '饵'=>'餌', '饥'=>'飢', '饭'=>'飯', '馆'=>'館', '马'=>'馬', '验'=>'驗', '驻'=>'駐', '驼'=>'駝', '驾'=>'駕', '骂'=>'罵', '骄'=>'驕', '骚'=>'騷', '鱼'=>'魚', '鸟'=>'鳥', '鸡'=>'雞', '鸣'=>'鳴', '鸭'=>'鴨', '鸿'=>'鴻', '鹅'=>'鵝', '鹏'=>'鵬', '鹤'=>'鶴', '鹰'=>'鷹', '黄'=>'黃', '齐'=>'齊', '齿'=>'齒', '龙'=>'龍', '买'=>'買', '卖'=>'賣', '个'=>'個', '们'=>'們', '见'=>'見', '观'=>'觀', '话'=>'話', '说'=>'說', '请'=>'請', '诸'=>'諸', '读'=>'讀', '购'=>'購', '费'=>'費', '资'=>'資', '输'=>'輸', '边'=>'邊', '过'=>'過', '达'=>'達', '运'=>'運', '连'=>'連', '选'=>'選', '释'=>'釋', '银'=>'銀', '开'=>'開', '关'=>'關', '门'=>'門', '闩'=>'閂', '闪'=>'閃', '闹'=>'鬧', '闻'=>'聞', '阎'=>'閻', '长'=>'長', '电'=>'電', '灵'=>'靈', '发货'=>'發貨', '收货'=>'收貨', '确认'=>'確認', '购买'=>'購買', '评论'=>'評論', '卖家'=>'賣家', '买家'=>'買家', '运费'=>'運費', '详细'=>'詳細', '收件'=>'收件', '联系'=>'聯絡', '在线'=>'在線', '客服'=>'客服', '设置'=>'設定', '话费'=>'話費', ' geld '=>' geld '];}return strtr(strval($s),$m);}
function uni_initial($s){ $s=strval($s); if($s==='')return 'K'; if(function_exists('mb_substr')){ try{ return mb_substr($s,0,1,'UTF-8'); }catch(Throwable $e){} } if(preg_match('/^./us',$s,$m))return $m[0]; return substr($s,0,1); }
define('DATA_DIR', __DIR__.'/data'); @mkdir(DATA_DIR,0777,true);
require_once __DIR__.'/db.php';
define('ADMIN_USER','admin'); define('ADMIN_PASS','123456');
session_start();
$page=$_GET['page']??'detail';
function df($c){return DATA_DIR.'/conv_'.preg_replace('/[^a-zA-Z0-9_-]/','',$c).'.json';}
function lm($c){$c=preg_replace('/[^a-zA-Z0-9_-]/','',$c);if($p=db()){try{$s=$p->prepare("SELECT sender,text,ts,mtime,`read`,img FROM messages WHERE conv=? ORDER BY id");$s->execute([$c]);return array_map(fn($r)=>['from'=>$r['sender'],'text'=>$r['text'],'ts'=>$r['ts'],'time'=>intval($r['mtime']),'read'=>(bool)$r['read'],'img'=>$r['img']??''],$s->fetchAll());}catch(Throwable $e){ try{$s=$p->prepare("SELECT sender,text,ts,mtime,`read` FROM messages WHERE conv=? ORDER BY id");$s->execute([$c]);return array_map(fn($r)=>['from'=>$r['sender'],'text'=>$r['text'],'ts'=>$r['ts'],'time'=>intval($r['mtime']),'read'=>(bool)$r['read'],'img'=>''],$s->fetchAll());}catch(Throwable $e2){}}}
 $f=df($c);if(!file_exists($f))return[];$a=json_decode(@file_get_contents($f),true);return is_array($a)?$a:[];}
function sm($c,$a){$a=array_values($a);$c=preg_replace('/[^a-zA-Z0-9_-]/','',$c);if($p=db()){try{$p->prepare("DELETE FROM messages WHERE conv=?")->execute([$c]);$s=$p->prepare("INSERT INTO messages(conv,sender,text,ts,mtime,`read`,img)VALUES(?,?,?,?,?,?,?)");foreach($a as $m){$s->execute([$c,($m['from']??'user')==='admin'?'admin':'user',strval($m['text']??''),strval($m['ts']??''),intval($m['time']??time()),empty($m['read'])?0:1,strval($m['img']??'')]);}return;}catch(Throwable $e){}}
 file_put_contents(df($c),json_encode($a,JSON_UNESCAPED_UNICODE));}
function mappend($c,$from,$text,$img=''){$c=preg_replace('/[^a-zA-Z0-9_-]/','',$c);if($p=db()){try{$p->prepare("INSERT INTO messages(conv,sender,text,ts,mtime,`read`,img)VALUES(?,?,?,?,?,?,?)")->execute([$c,$from,$text,date('m-d H:i'),time(),$from==='admin'?1:0,$img]);return true;}catch(Throwable $e){}}return false;}
function get_visitor_id(){if(!empty($_COOKIE['visitor_session'])){return preg_replace('/[^a-zA-Z0-9_-]/','',strval($_COOKIE['visitor_session']));}if(!session_id())session_start();if(empty($_SESSION['visitor_id']))$_SESSION['visitor_id']=bin2hex(random_bytes(8));setcookie('visitor_session',$_SESSION['visitor_id'],time()+604800,'/',null,false,true);return $_SESSION['visitor_id'];}
function bfile(){return DATA_DIR.'/banned.json';}
function lbanned(){$f=bfile();if(!file_exists($f)){file_put_contents($f,json_encode([],JSON_UNESCAPED_UNICODE));return [];}$a=json_decode(@file_get_contents($f),true);return is_array($a)?$a:[];}
function sbanned($a){$a=array_values($a);file_put_contents(bfile(),json_encode($a,JSON_UNESCAPED_UNICODE));if($p=db()){try{$p->exec("DELETE FROM banned");foreach($a as $b)$p->prepare("INSERT INTO banned(conv,reason,time)VALUES(?,?,?)")->execute([strval($b['conv']),strval($b['reason']??''),strval($b['time']??'')]);}catch(Throwable $e){}}}
function is_banned($conv){$conv=preg_replace('/[^a-zA-Z0-9_-]/','',$conv);foreach(lbanned() as $b){if($b['conv']===$conv)return $b;}return false;}
function sfile(){return DATA_DIR.'/settings.json';}
function lset(){ $def=['support_name'=>'在线客服','support_avatar'=>'','support_hello'=>'您好，这里是在线客服，有什么可以帮您？','share_title'=>'','share_description'=>'','share_image'=>'','platform_name'=>'Goofish','support_description'=>'','support_greeting'=>'您好，这里是在线客服，有什么可以帮您？','admin_pass'=>'123456'];
 if(!file_exists(sfile())){file_put_contents(sfile(),json_encode($def,JSON_UNESCAPED_UNICODE));}else{$f=json_decode(@file_get_contents(sfile()),true);if(is_array($f))$def=array_merge($def,$f);}
 if($p=db()){try{$rows=$p->query("SELECT k,v FROM settings")->fetchAll();if(empty($rows)){ $s=$p->prepare("REPLACE INTO settings(k,v)VALUES(?,?)");foreach($def as $k=>$v)$s->execute([$k,strval($v)]); }else{foreach($rows as $r)$def[$r['k']]=$r['v'];}}catch(Throwable $e){}}
 return $def;}
function sset($a){file_put_contents(sfile(),json_encode($a,JSON_UNESCAPED_UNICODE));if($p=db()){try{$s=$p->prepare("REPLACE INTO settings(k,v)VALUES(?,?)");foreach($a as $k=>$v)$s->execute([strval($k),strval($v)]);}catch(Throwable $e){}}}
function admin_pass(){ $s=lset(); return strval($s['admin_pass']??'123456'); }
function pfile(){return DATA_DIR.'/products.json';}
function norm_prod($x){
 $d=$x['data']??[];
 $def=['title'=>$x['title']??$d['title']??'离岛16','price'=>$x['price']??$d['price']??'2990','free_shipping'=>$d['free_shipping']??true,'negotiable'=>$d['negotiable']??true,'tags'=>$d['tags']??['手机平板','HK 香港'],'wants'=>$d['wants']??0,'views'=>$d['views']??10,'favorites'=>$d['favorites']??12,'description'=>$d['description']??$x['desc']??'便宜漂亮，需要的来谢谢','listed_at'=>$d['listed_at']??'1周前发布','seller_name'=>$d['seller_name']??'Kk','seller_badge'=>$d['seller_badge']??'商户','seller_initial'=>$d['seller_initial']??'K','seller_active'=>$d['seller_active']??'4日前活跃','sold_count'=>$d['sold_count']??0,'condition'=>$d['condition']??'几乎全新'];
 $imgs=$x['images']??[]; if(empty($imgs)&&!empty($x['img']))$imgs=[$x['img']];
 return ['id'=>$x['id'],'payment_link_id'=>intval($x['payment_link_id']??0),'status'=>$x['status']??'已发布','pay'=>$x['pay']??'AlipayHK','avatar'=>$x['avatar']??'','images'=>$imgs,'data'=>$def];
}
function prow($r){return norm_prod(['id'=>intval($r['id']),'payment_link_id'=>intval($r['payment_link_id']??0),'status'=>$r['status']??'已发布','pay'=>$r['pay']??'AlipayHK','avatar'=>$r['avatar']??'','images'=>json_decode(strval($r['images']??'[]'),true)?:[],'data'=>json_decode(strval($r['datum']??'{}'),true)?:[]]);}
function lprod(){if($p=db()){try{$a=array_map('prow',$p->query("SELECT * FROM products ORDER BY id")->fetchAll());if(empty($a)){$d=[norm_prod(['id'=>40,'payment_link_id'=>1,'status'=>'已发布','data'=>['title'=>'离岛16','price'=>'2990']]),norm_prod(['id'=>39,'payment_link_id'=>1,'status'=>'已发布','data'=>['title'=>'离岛16','price'=>'3290']])];sprod($d);return $d;}return $a;}catch(Throwable $e){}}
 if(!file_exists(pfile())){$d=[norm_prod(['id'=>40,'payment_link_id'=>1,'status'=>'已发布','data'=>['title'=>'离岛16','price'=>'2990']]),norm_prod(['id'=>39,'payment_link_id'=>1,'status'=>'已发布','data'=>['title'=>'离岛16','price'=>'3290']])];file_put_contents(pfile(),json_encode($d,JSON_UNESCAPED_UNICODE));return $d;}$a=json_decode(@file_get_contents(pfile()),true);if(!is_array($a))return[];return array_map('norm_prod',$a);}
function sprod($a){$a=array_values($a);if($p=db()){try{$ids=array_map(fn($x)=>intval($x['id']),$a);$s=$p->prepare("REPLACE INTO products(id,payment_link_id,status,pay,avatar,images,datum)VALUES(?,?,?,?,?,?,?)");foreach($a as $x){$d=$x['data']??[];$s->execute([intval($x['id']),intval($x['payment_link_id']??0),strval($x['status']??'已发布'),strval($x['pay']??'AlipayHK'),strval($x['avatar']??''),json_encode(array_values($x['images']??[]),JSON_UNESCAPED_UNICODE),json_encode($d,JSON_UNESCAPED_UNICODE)]);}if(empty($ids)){$p->exec("DELETE FROM products");}else{$p->exec("DELETE FROM products WHERE id NOT IN (".implode(',',array_map('intval',$ids)).")");}return;}catch(Throwable $e){}}
 file_put_contents(pfile(),json_encode($a,JSON_UNESCAPED_UNICODE));}
function lfile(){return DATA_DIR.'/links.json';}
function llinks(){if($p=db()){try{$a=$p->query("SELECT * FROM links ORDER BY id")->fetchAll();if(empty($a)){$d=[['id'=>1,'name'=>'AlipayHK','url'=>'https://example.com/pay/alipayhk-demo','remark'=>'默认收款']];slinks($d);return $d;}return array_map(fn($r)=>['id'=>intval($r['id']),'name'=>$r['name'],'url'=>$r['url'],'remark'=>$r['remark']??''],$a);}catch(Throwable $e){}}
 if(!file_exists(lfile())){$d=[['id'=>1,'name'=>'AlipayHK','url'=>'https://example.com/pay/alipayhk-demo','remark'=>'默认收款']];file_put_contents(lfile(),json_encode($d,JSON_UNESCAPED_UNICODE));return $d;}$a=json_decode(@file_get_contents(lfile()),true);return is_array($a)?$a:[];}
function slinks($a){$a=array_values($a);if($p=db()){try{$ids=[];foreach($a as $l){$id=intval($l['id']??0);if($id>0){$p->prepare("REPLACE INTO links(id,name,url,remark)VALUES(?,?,?,?)")->execute([$id,strval($l['name']??''),strval($l['url']??''),strval($l['remark']??'')]);$ids[]=$id;}else{$p->prepare("INSERT INTO links(name,url,remark)VALUES(?,?,?)")->execute([strval($l['name']??''),strval($l['url']??''),strval($l['remark']??'')]);$ids[]=intval($p->lastInsertId());}}if(empty($ids)){$p->exec("DELETE FROM links");}else{$p->exec("DELETE FROM links WHERE id NOT IN (".implode(',',array_map('intval',$ids)).")");}return;}catch(Throwable $e){}}
 file_put_contents(lfile(),json_encode(array_values($a),JSON_UNESCAPED_UNICODE));}
function link_by_id($id){foreach(llinks() as $l){if($l['id']==$id)return $l;}return llinks()[0]??['id'=>0,'name'=>'AlipayHK','url'=>'','remark'=>''];}
function link_name($p){if(!empty($p['payment_link_id']))return link_by_id(intval($p['payment_link_id']))['name'];return $p['pay']??'AlipayHK';}
function l_link_id_exists($lid){foreach(llinks() as $l){if(intval($l['id'])===$lid)return true;}return false;}
function alipay_scheme($url){
  return 'alipayhk://platformapi/startapp?saId=10000007&clientVersion=3.7.0.0718&qrcode='.rawurlencode($url);
}
function last_addr(){if(!empty($_SESSION['my_addr'])&&!empty($_SESSION['my_addr']['name']))return $_SESSION['my_addr'];return null;}
function gen_oid(){return 'GO'.date('YmdHis').str_pad(mt_rand(0,999),3,'0',STR_PAD_LEFT);}
function order_add($name,$phone,$city,$addr,$amount=''){
 $t=date('Y-m-d H:i:s'); $oid=gen_oid();
 $_SESSION['my_addr']=['name'=>$name,'phone'=>$phone,'city'=>$city,'addr'=>$addr,'amount'=>$amount,'time'=>$t];
 if($p=db()){try{$p->prepare("INSERT INTO orders(orderid,time,name,phone,addr)VALUES(?,?,?,?,?)")->execute([$oid,$t,$name,$phone,trim($city.' '.$addr)]);return $oid;}catch(Throwable $e){}}
 @file_put_contents(DATA_DIR.'/orders.log',$t."|".$oid."|".$name."|".$phone."|".$city." ".$addr.PHP_EOL,FILE_APPEND);
 return $oid;}
function cur_prod(){ $a=lprod(); $id=intval($_GET['id']??0);
 if($id){ foreach($a as $x){ if(intval($x['id'])===$id&&($x['status']??'')==='已发布'&&intval($x['payment_link_id']??0)>0&&l_link_id_exists(intval($x['payment_link_id']))) return $x; } return null; }
 foreach($a as $x){ if(($x['status']??'')==='已发布'&&l_link_id_exists(intval($x['payment_link_id']))) return $x; }
 return null;
}
function pimg_url($p){ if(!empty($p['images'][0])&&file_exists(__DIR__.'/'.$p['images'][0])) return $p['images'][0]; return ''; }
function ptitle($p){$d=$p['data']??[];return strval($d['title']??$p['title']??'');}
function pprice($p){$d=$p['data']??[];$v=strval($d['price']??$p['price']??'');$v=preg_replace('/^HK\$?/','',$v);return 'HKD'.$v;}
if(isset($_GET['api'])){
 header('Content-Type: application/json; charset=utf-8');
 $api=$_GET['api'];$conv=$_POST['conv']??$_GET['conv']??'';
 if($conv===''||$conv==='default')$conv=get_visitor_id();
 if($api==='send'){$raw=strval($_POST['text']??'');$raw=trim($raw);
  // 前端做了 encodeURIComponent + URLSearchParams 双重编码，这里最多解两次，还原出 data:image/ 开头
  $t=$raw; if(strpos($t,'%')!==false){ $d1=urldecode($t); if(strpos($d1,'data:image/')===0)$t=$d1; }
  $t=trim($t);
  $isImg=(strpos($t,'data:image/')===0);
  if(!$isImg){ $t=function_exists('mb_substr')?mb_substr($t,0,1000):substr($t,0,1000); }else{ if(strlen($t)>3000000){echo json_encode(['ok'=>0,'msg'=>'图片太大']);exit;} }
  $f=($_POST['from']??'user')==='admin'?'admin':'user';
  if($f==='admin'&&empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}
  if($f==='user'){$_b=is_banned($conv);if($_b!==false){echo json_encode(['ok'=>0,'msg'=>'您已被拉黑，无法发送消息']);exit;}}
  if($t===''){echo json_encode(['ok'=>0]);exit;}
  $img='';
  $clean=preg_replace('/\s+/','',trim($t));
  if(strpos($clean,'data:image/')===0){if(preg_match('/^data:image\/([a-zA-Z0-9+]+);base64,([A-Za-z0-9+\/=]+)$/',$clean,$m2)){ $ext=strtolower($m2[1]);if($ext==='jpeg')$ext='jpg';if(!in_array($ext,['jpg','png','gif','webp']))$ext='png';$nm='assets/cmsg_'.time().'_'.mt_rand(0,9999).'.'.$ext;@mkdir(__DIR__.'/assets',0777,true);$bin=base64_decode($m2[2],true);if($bin!==false&&$bin!==''&&file_put_contents(__DIR__.'/'.$nm,$bin)){$img=$nm;}}}
  $saveText=$img!==''?'':$t;
  if(!mappend($conv,$f,$saveText,$img)){$m=lm($conv);$m[]=['from'=>$f,'text'=>$saveText,'ts'=>date('m-d H:i'),'time'=>time(),'read'=>$f==='admin','img'=>$img];sm($conv,$m);}echo json_encode(['ok'=>1]);exit;}
 if($api==='list'){echo json_encode(['ok'=>1,'msgs'=>lm($conv)]);exit;}
 if($api==='mark'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$c=preg_replace('/[^a-zA-Z0-9_-]/','',strval($_POST['conv']??$_GET['conv']??'default'));if($p=db()){try{$p->prepare("UPDATE messages SET `read`=1 WHERE conv=? AND sender='user'")->execute([$c]);echo json_encode(['ok'=>1]);exit;}catch(Throwable $e){}}$m=lm($c);foreach($m as&$x){if($x['from']==='user')$x['read']=true;}sm($c,$m);echo json_encode(['ok'=>1]);exit;}
 if($api==='convs'){header('Content-Type: application/json; charset=utf-8');try{if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0,'msg'=>'not_login']);exit;}$o=[];$seen=[];try{$pdo=db();if($pdo){foreach($pdo->query("SELECT conv,COUNT(*)c,SUM(sender='user' AND `read`=0)u FROM messages GROUP BY conv")->fetchAll() as $r){$cid=strval($r['conv']??'');if($cid==='')continue;$t=null;try{$l=$pdo->prepare("SELECT sender,text,ts,img FROM messages WHERE conv=? ORDER BY id DESC LIMIT 1");$l->execute([$cid]);$t=$l->fetch();}catch(Throwable $e2){try{$l=$pdo->prepare("SELECT sender,text,ts FROM messages WHERE conv=? ORDER BY id DESC LIMIT 1");$l->execute([$cid]);$t=$l->fetch();}catch(Throwable $e3){$t=null;}}$txt=$t?strval($t['text']??''):'';$img=$t?strval($t['img']??''):'';if($txt!==''&&strpos(preg_replace('/\s+/','',$txt),'data:image/')===0)$img=$img!==''?$img:'data';$tp='';if($txt!==''){$tp=function_exists('mb_substr')?@mb_substr($txt,0,20,'UTF-8'):substr($txt,0,20);}if($img!=='')$tp='📷 '.$tp;$m=$t?['from'=>strval($t['sender']??'user'),'text'=>$tp,'ts'=>strval($t['ts']??'')]:null;$o[]=['id'=>$cid,'count'=>intval($r['c']??0),'last'=>$m,'unread'=>intval($r['u']??0)];$seen[$cid]=1;}}}catch(Throwable $e){}try{foreach((glob(DATA_DIR.'/conv_*.json')?:[]) as $f){try{$id=substr(basename($f,'.json'),5);if(isset($seen[$id]))continue;if(!preg_match('/^[a-zA-Z0-9_-]+$/',$id))continue;$raw=@file_get_contents($f);if($raw===false||$raw==='')continue;$m=@json_decode($raw,true);if(!is_array($m)||empty($m))continue;$l=end($m);$u=0;$cnt=count($m);foreach($m as$x){if(($x['from']??'')==='user'&&empty($x['read']))$u++;}$tp='';if($l&&!empty($l['text']))$tp=function_exists('mb_substr')?@mb_substr(strval($l['text']),0,20,'UTF-8'):substr(strval($l['text']),0,20);if($l&&(!empty($l['img'])||strpos(strval($l['text']??''),'data:image/')===0))$tp='📷 '.$tp;$o[]=['id'=>$id,'count'=>$cnt,'last'=>$m?['from'=>strval($l['from']??'user'),'text'=>$tp,'ts'=>strval($l['ts']??'')]:null,'unread'=>$u];}catch(Throwable $e4){continue;}}}catch(Throwable $e){}echo json_encode(['ok'=>1,'list'=>$o]);}catch(Throwable $e){echo json_encode(['ok'=>1,'list'=>[],'warn'=>'fallback']);}exit;}
 if($api==='order'){echo json_encode(['ok'=>1,'oid'=>order_add($_POST['name']??'',$_POST['phone']??'',$_POST['city']??'',$_POST['addr']??'',$_POST['amount']??'')]);exit;}
 if($api==='orders'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$rows=[];if($p=db()){try{foreach($p->query("SELECT * FROM orders ORDER BY id DESC")->fetchAll() as $r){$rows[]=['id'=>strval($r['orderid']??$r['id']),'time'=>$r['time'],'name'=>$r['name'],'phone'=>$r['phone'],'addr'=>$r['addr'],'amount'=>$r['amount']??'HKD2,497.14','status'=>$r['status']??'待发货'];}echo json_encode(['ok'=>1,'list'=>$rows]);exit;}catch(Throwable $e){}}if(file_exists(DATA_DIR.'/orders.log')){$lines=array_values(array_filter(array_map('trim',@file(DATA_DIR.'/orders.log')?:[]),fn($x)=>$x!==''));foreach(array_reverse($lines,true) as $idx=>$ln){$pp=explode('|',$ln);if(count($pp)>=5){$rows[]=['id'=>trim($pp[1]),'time'=>$pp[0],'name'=>$pp[2],'phone'=>$pp[3],'addr'=>$pp[4],'amount'=>'HKD2,497.14','status'=>'待发货'];}else{$rows[]=['id'=>'旧#'.($idx+1),'time'=>$pp[0]??'','name'=>$pp[1]??'','phone'=>$pp[2]??'','addr'=>$pp[3]??'','amount'=>'HKD2,497.14','status'=>'待发货'];}}}echo json_encode(['ok'=>1,'list'=>$rows]);exit;}
 if($api==='odel'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$id=trim(strval($_POST['id']??''));if($p=db()){try{if(preg_match('/^GO\d+$/',$id)){$p->prepare("DELETE FROM orders WHERE orderid=?")->execute([$id]);}else{$p->prepare("DELETE FROM orders WHERE id=?")->execute([intval($id)]);}echo json_encode(['ok'=>1]);exit;}catch(Throwable $e){}}if(file_exists(DATA_DIR.'/orders.log')){$lines=array_values(array_filter(array_map('trim',@file(DATA_DIR.'/orders.log')?:[]),fn($x)=>$x!==''));foreach($lines as $i=>$ln){$pp=explode('|',trim($ln));if(trim($pp[1]??'')===$id){array_splice($lines,$i,1);break;}}file_put_contents(DATA_DIR.'/orders.log',empty($lines)?'':implode(PHP_EOL,$lines).PHP_EOL);}echo json_encode(['ok'=>1]);exit;}
 if($api==='osave'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$id=trim(strval($_POST['id']??''));$nm=trim(strval($_POST['name']??''));$ph=trim(strval($_POST['phone']??''));$ad=trim(strval($_POST['addr']??''));if($p=db()){try{if(preg_match('/^GO\d+$/',$id)){$p->prepare("UPDATE orders SET name=?,phone=?,addr=? WHERE orderid=?")->execute([$nm,$ph,$ad,$id]);}else{$p->prepare("UPDATE orders SET name=?,phone=?,addr=? WHERE id=?")->execute([$nm,$ph,$ad,intval($id)]);}echo json_encode(['ok'=>1]);exit;}catch(Throwable $e){}}if(file_exists(DATA_DIR.'/orders.log')){$lines=array_values(array_filter(array_map('trim',@file(DATA_DIR.'/orders.log')?:[]),fn($x)=>$x!==''));foreach($lines as $i=>$ln){$pp=explode('|',trim($ln));if(trim($pp[1]??'')===$id){$lines[$i]=implode('|',[$pp[0]??'',$id,$nm,$ph,$ad]);file_put_contents(DATA_DIR.'/orders.log',implode(PHP_EOL,$lines).PHP_EOL);break;}}}echo json_encode(['ok'=>1]);exit;}
 if($api==='plist'){$l=lprod();foreach($l as&$x){$x['linkName']=link_name($x);}echo json_encode(['ok'=>1,'list'=>$l,'links'=>llinks()]);exit;}
 if($api==='psave'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$a=lprod();$id=intval($_POST['id']??0);
  $data=json_decode($_POST['data']??'{}',true);if(!is_array($data))$data=[];
  $imgs=json_decode($_POST['images']??'[]',true);if(!is_array($imgs))$imgs=[];
  $row=norm_prod(['id'=>$id?:(count($a)?max(array_column($a,'id'))+1:40),'payment_link_id'=>intval($_POST['payment_link_id']??0),'status'=>$_POST['status']??'已发布','pay'=>$_POST['pay']??'AlipayHK','avatar'=>$_POST['avatar']??'','images'=>$imgs,'data'=>$data]);
  $found=false;foreach($a as&$x){if($x['id']==$row['id']){$x=$row;$found=true;}}if(!$found)$a[]=$row;sprod($a);echo json_encode(['ok'=>1,'id'=>$row['id']]);exit;}
 if($api==='pcopy'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$a=lprod();foreach($a as $x){if($x['id']==intval($_POST['id']??0)){$n=$x;$n['id']=max(array_column($a,'id'))+1;$a[]=$n;}}sprod($a);echo json_encode(['ok'=>1]);exit;}
 if($api==='pdel'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$a=array_values(array_filter(lprod(),fn($x)=>$x['id']!=intval($_POST['id']??0)));sprod($a);echo json_encode(['ok'=>1]);exit;}
 if($api==='pupload'){if(empty($_FILES['file']['tmp_name'])){echo json_encode(['ok'=>0,'msg'=>'无文件']);exit;}
  $ext=strtolower(pathinfo($_FILES['file']['name']??'a.jpg',PATHINFO_EXTENSION));if(!in_array($ext,['jpg','jpeg','png','webp']))$ext='jpg';
  $nm='assets/p_'.time().'.'.$ext;@mkdir(__DIR__.'/assets',0777,true);
  if(move_uploaded_file($_FILES['file']['tmp_name'],__DIR__.'/'.$nm)){echo json_encode(['ok'=>1,'url'=>$nm]);exit;}
  echo json_encode(['ok'=>0,'msg'=>'上传失败']);exit;}
 if($api==='llist'){echo json_encode(['ok'=>1,'list'=>llinks()]);exit;}
 if($api==='lsave'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$a=llinks();$id=intval($_POST['id']??0);
  $row=['id'=>$id?:(count($a)?max(array_column($a,'id'))+1:1),'name'=>$_POST['name']??'','url'=>trim($_POST['url']??''),'remark'=>$_POST['remark']??''];
  $f=false;foreach($a as&$x){if($x['id']==$row['id']){$x=$row;$f=true;}}if(!$f)$a[]=$row;slinks($a);echo json_encode(['ok'=>1]);exit;}
 if($api==='ldel'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$lid=intval($_POST['id']??0);$al=llinks();$a=array_values(array_filter($al,fn($x)=>$x['id']!=$lid));slinks($a);
  $pa=lprod();foreach($pa as &$x){if(intval($x['payment_link_id']??0)===$lid)$x['status']='已下架';}sprod($pa);
  echo json_encode(['ok'=>1]);exit;}
 if($api==='proof'){if($p=db()){try{$p->prepare("INSERT INTO proofs(time,oid,txn,msg)VALUES(?,?,?,?)")->execute([date('Y-m-d H:i:s'),strval($_POST['order']??''),strval($_POST['txn']??''),strval($_POST['msg']??'')]);}catch(Throwable $e){@file_put_contents(DATA_DIR.'/proofs.log',date('Y-m-d H:i:s')."|".($_POST['order']??'')."|".($_POST['txn']??'')."|".($_POST['msg']??'').PHP_EOL,FILE_APPEND);}}else{@file_put_contents(DATA_DIR.'/proofs.log',date('Y-m-d H:i:s')."|".($_POST['order']??'')."|".($_POST['txn']??'')."|".($_POST['msg']??'').PHP_EOL,FILE_APPEND);}echo json_encode(['ok'=>1]);exit;}
 if($api==='sget'){echo json_encode(['ok'=>1,'set'=>lset()]);exit;}
 if($api==='syssave'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$s=lset();foreach(['share_title','share_description','share_image','platform_name','support_name','support_avatar','support_description','support_greeting'] as $k){if(isset($_POST[$k]))$s[$k]=trim(strval($_POST[$k]));}$s['support_hello']=$s['support_greeting'];sset($s);echo json_encode(['ok'=>1]);exit;}
 if($api==='pwchange'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0,'msg'=>'未登录']);exit;}$s=lset();$old=strval($_POST['old']??'');$new=strval($_POST['new']??'');
  if($old!==strval($s['admin_pass']??'')){echo json_encode(['ok'=>0,'msg'=>'原密码不正确']);exit;}
  if(strlen($new)<6){echo json_encode(['ok'=>0,'msg'=>'新密码至少6位']);exit;}
  $s['admin_pass']=$new;sset($s);unset($_SESSION['is_admin']);echo json_encode(['ok'=>1]);exit;}
 if($api==='lblock'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}echo json_encode(['ok'=>1,'list'=>lbanned()]);exit;}
 if($api==='block'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$conv=preg_replace('/[^a-zA-Z0-9_-]/','',strval($_POST['conv']??''));$reason=trim(strval($_POST['reason']??' banned'));if($conv===''||$conv==='admin'){echo json_encode(['ok'=>0]);exit;}$banned=lbanned();$found=false;foreach($banned as $i=>$b){if($b['conv']===$conv){if($reason==='unblock'){array_splice($banned,$i,1);}else{$banned[$i]=['conv'=>$conv,'reason'=>$reason,'time'=>date('Y-m-d H:i')];} $found=true;break;}}if(!$found&&$reason!=='unblock')$banned[]=['conv'=>$conv,'reason'=>$reason,'time'=>date('Y-m-d H:i')];sbanned($banned);echo json_encode(['ok'=>1]);exit;}
 if($api==='cdel'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$c=preg_replace('/[^a-zA-Z0-9_-]/','',strval($_POST['conv']??''));if($c===''){echo json_encode(['ok'=>0]);exit;}if($p=db()){try{$p->prepare("DELETE FROM messages WHERE conv=?")->execute([$c]);}catch(Throwable $e){}}@unlink(df($c));echo json_encode(['ok'=>1]);exit;}
 if($api==='dbstatus'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0]);exit;}$p=db();if(!$p){echo json_encode(['ok'=>1,'db'=>0,'msg'=>'未连接（文件存储中）']);exit;}$c=[];try{foreach(['products','links','settings','messages','orders','proofs','banned'] as $t){$c[$t]=intval($p->query("SELECT COUNT(*)c FROM $t")->fetch()['c']);}}catch(Throwable $e){}echo json_encode(['ok'=>1,'db'=>1,'counts'=>$c]);exit;}
 if($api==='dbimport'){if(empty($_SESSION['is_admin'])){echo json_encode(['ok'=>0,'msg'=>'未登录']);exit;}$p=db();if(!$p){echo json_encode(['ok'=>0,'msg'=>'数据库未连接']);exit;}$n=['products'=>0,'orders'=>0,'messages'=>0];
  try{
   if(file_exists(pfile())){foreach((json_decode(@file_get_contents(pfile()),true)?:[]) as $x){$x=norm_prod($x);$p->prepare("REPLACE INTO products(id,payment_link_id,status,pay,avatar,images,datum)VALUES(?,?,?,?,?,?,?)")->execute([intval($x['id']),intval($x['payment_link_id']??0),strval($x['status']??'已发布'),strval($x['pay']??'AlipayHK'),strval($x['avatar']??''),json_encode(array_values($x['images']??[]),JSON_UNESCAPED_UNICODE),json_encode($x['data']??[],JSON_UNESCAPED_UNICODE)]);$n['products']++;}}
   if(file_exists(DATA_DIR.'/orders.log')){foreach(array_filter(array_map('trim',@file(DATA_DIR.'/orders.log')?:[])) as $ln){$pp=explode('|',trim($ln));if(count($pp)>=5){$oid=trim($pp[1]);$nm=$pp[2];$ph=$pp[3];$ad=$pp[4];$tm=$pp[0];}else{$oid=gen_oid();$nm=$pp[1]??'';$ph=$pp[2]??'';$ad=$pp[3]??'';$tm=$pp[0]??'';}try{$p->prepare("INSERT INTO orders(orderid,time,name,phone,addr)VALUES(?,?,?,?,?)")->execute([$oid,$tm,$nm,$ph,$ad]);$n['orders']++;}catch(Throwable $e){}}}
  }catch(Throwable $e){echo json_encode(['ok'=>0,'msg'=>'导入中断：'.$e->getMessage(),'done'=>$n]);exit;}
  echo json_encode(['ok'=>1,'done'=>$n]);exit;}
 exit;
}
if($page==='admin_login'&&$_SERVER['REQUEST_METHOD']==='POST'){if(($_POST['u']??'')===ADMIN_USER&&($_POST['p']??'')===admin_pass()){$_SESSION['is_admin']=1;header('Location: ?page=admin');exit;}$err='账号或密码错误';}
if($page==='logout'){unset($_SESSION['is_admin']);header('Location: ?page=admin_login');exit;}
$P=cur_prod(); $PIMG=pimg_url($P);
$PDeleted=(!$P);
$isAdminPage=($page==='admin'||$page==='admin_login');
$SYS=lset(); $SH_T=trim(strval($SYS['share_title']??''));
?>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title><?= $isAdminPage?'管理工作台':($SH_T!==''?$SH_T:'闲鱼') ?></title>
<?php if(!$isAdminPage): $BASE=(isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off'?'https':'http').'://'.$_SERVER['HTTP_HOST']; $SI=trim(strval($SYS['share_image']??'')); $SH_D=trim(strval($SYS['share_description']??'')); $SH_PN=trim(strval($SYS['platform_name']??'')); $SH_IT=trim(strval($SYS['share_title']??'')); if($SH_IT||$SH_D||$SI): $_ogImg='';if($SI!==''){$_raw=$SI;if(strpos($_raw,'://')!==false){$_ogImg=$_raw;}else{$_prep=$_raw[0]==='/'?substr($_raw,1):$_raw;$_ogImg=$_prep!==''?$BASE.'/'.str_replace('//','/',$_prep):'';}}?><meta property="og:type" content="website"><meta property="og:title" content="<?=htmlspecialchars($SH_IT?:$SH_PN)?>"><meta property="og:description" content="<?=htmlspecialchars($SH_D)?>"><meta property="og:url" content="<?=htmlspecialchars($BASE.$_SERVER['REQUEST_URI'])?>"><?php if($_ogImg): ?><meta property="og:image" content="<?=htmlspecialchars($_ogImg)?>"><?php endif; ?><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="<?=htmlspecialchars($SH_IT?:$SH_PN)?>"><meta name="twitter:description" content="<?=htmlspecialchars($SH_D)?>"><?php if($_ogImg): ?><meta name="twitter:image" content="<?=htmlspecialchars($_ogImg)?>"><?php endif; ?><?php endif; endif; ?><style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{background:<?= $isAdminPage?'#f4f6fa':'#e9e9e9' ?>;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:#1a1a1a;<?= $isAdminPage?'':'display:flex;justify-content:center' ?>}
<?php if(!$isAdminPage): ?>
.phone{width:430px;max-width:100%;min-height:100vh;background:#fff;position:relative}
.d-top{display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fff;position:sticky;top:0;z-index:5}
.bk{width:30px;height:38px;display:flex;align-items:center;justify-content:center;font-size:26px;color:#111;text-decoration:none}
.d-search{flex:1;background:#f2f2f4;border-radius:20px;height:38px;display:flex;align-items:center;padding:0 14px;color:#999;font-size:14px}
.d-seller{display:flex;align-items:center;gap:10px;padding:8px 16px}
.av{width:42px;height:42px;border-radius:50%;background:#FFB800;padding:2px}
.av i{width:100%;height:100%;border-radius:50%;background:#FFD84D;display:flex;align-items:center;justify-content:center;font-style:normal;font-size:24px}
.nm{font-size:16px;font-weight:700}.sub2{font-size:12px;color:#999}
.fol{margin-left:auto;border:1px solid #eee;border-radius:16px;padding:6px 16px;font-size:13px}
.d-body{padding:8px 16px 90px}
.pr{font-size:23px;font-weight:800}
.meta{font-size:12.5px;color:#999;margin:8px 0;display:flex;gap:6px}
.meta .tag{color:#4caf50;font-size:12px;font-weight:700}
.guarantee{font-size:13px;color:#1677ff;margin:10px 0;font-weight:600}
.steps{display:flex;background:#f6f8fb;border-radius:10px;padding:12px 4px;margin:6px 0 10px;text-align:center}
.steps div{flex:1}.steps b{display:block;font-size:12.5px}.steps span{font-size:11px;color:#999}
.desc{font-size:15.5px;margin:10px 0;color:#222}
.pimg{border-radius:10px;overflow:hidden;background:#f4f4f4;margin-bottom:10px}.pimg img{width:100%;display:block}
.d-bottom{position:fixed;bottom:0;width:430px;max-width:100%;background:#fff;border-top:1px solid #f0f0f0;display:flex;align-items:center;padding:10px 12px calc(10px + env(safe-area-inset-bottom));gap:6px}
.icb{width:52px;height:52px;display:flex;flex-direction:column;align-items:center;font-size:12px;color:#333;line-height:1.2;flex-shrink:0;padding-top:8px;gap:3px;white-space:nowrap}.icb i{font-style:normal;display:flex;align-items:center;height:25px}
.buy2{flex:1;display:flex;margin-left:6px;height:48px}.buy2 a{flex:1;display:flex;align-items:center;justify-content:center;text-decoration:none;font-size:15px}
.buy2 .b1{background:#f2f2f4;color:#111;border-radius:12px 0 0 12px}.buy2 .b2{background:#FFEB00;color:#111;font-weight:700;border-radius:0 12px 12px 0}
.tt{position:relative;text-align:center;font-weight:700;font-size:17px;padding:14px;background:#fff}.tt a{position:absolute;left:4px;top:50%;transform:translateY(-50%);width:48px;height:44px;display:flex;align-items:center;justify-content:center;font-size:24px;text-decoration:none;color:#111}
.card{background:#fff;border-radius:10px;margin:12px 14px;padding:16px 18px}
.muted{color:#999;font-size:13px}
.tiny{font-size:11px;color:#aaa;line-height:1.4}
.faint{color:#bbb}
.mt8{margin-top:8px}
.remark{font-size:12.5px;color:#888;line-height:1.5}
.row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;font-size:13px}
.tot{display:flex;justify-content:space-between;align-items:center;font-weight:800;font-size:15px;color:#111;border-top:1px solid #f0f0f0;margin-top:8px;padding-top:12px}
.pay{display:block;margin:12px 14px;background:#FFEB00;border-radius:26px;text-align:center;padding:15px;font-size:17px;font-weight:800;text-decoration:none;color:#111}
.frow{display:flex;gap:10px;padding:16px 2px;border-bottom:1px solid #f1f1f4;font-size:15px;align-items:center;color:#1a1a1a}
.frow input{border:0;outline:0;font-size:15px;flex:1;color:#1a1a1a;background:transparent}.frow input::placeholder{color:#c8c8ce}
.save{display:block;margin:120px 14px 20px;background:#FFEB00;border-radius:26px;text-align:center;padding:15px;font-weight:800;font-size:17px;text-decoration:none;color:#111}
.cb{height:60vh;overflow:auto;padding:14px;display:flex;flex-direction:column;gap:10px;background:#f5f5f5}
.m{max-width:75%;padding:10px 13px;border-radius:14px;font-size:14px;line-height:1.55;word-break:break-word}
.m.u{background:#fff;align-self:flex-start}.m.a{background:#FFEB00;align-self:flex-end}
.sb{background:#fff;display:flex;gap:6px;padding:8px 10px;border-top:1px solid #eee;align-items:flex-end}.sb input{flex:1;border:0;background:#f4f4f4;border-radius:20px;padding:10px 14px;outline:0;min-height:40px}.sb button{background:#FFEB00;border:0;border-radius:20px;padding:0 20px;font-weight:700;height:40px}.sb-img-btn{background:#f4f4f4;border:0;border-radius:20px;width:40px;height:40px;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}
<?php else: ?>
.wrap{display:flex;min-height:100vh}
.side{width:210px;background:#132337;color:#bfcbd9;flex-shrink:0;display:flex;flex-direction:column;padding:30px 20px}
.side h3{color:#fff;font-size:20px}
.mi{padding:12px 15px;border-radius:7px;font-size:14px;cursor:pointer;margin-bottom:2px;color:#bfcbd9}
.mi.on,.mi:hover{background:#2c435c;color:#fff}
.main{flex:1;padding:32px;min-width:0}
.mtitle{font-size:26px;font-weight:800;margin:5px 0 10px}
.bar{display:flex;justify-content:space-between;margin-bottom:16px;color:#718094;font-size:13px}
.tbl{background:#fff;border-radius:12px;overflow:auto;border:1px solid #e1e6ed}
table{width:100%;border-collapse:collapse;font-size:13.5px}
th{text-align:left;color:#6b7a8f;padding:15px;border-bottom:1px solid #edf0f5;background:#fafbfd;white-space:nowrap}
td{padding:15px;border-bottom:1px solid #edf0f5;vertical-align:middle}
.pill{background:#eef3f8;border-radius:5px;font-size:11px;padding:3px 8px;white-space:nowrap}
.op{display:inline-block;border:1px solid #dce2ea;color:#245a8e;border-radius:7px;padding:5px 10px;cursor:pointer;margin:3px;font-size:12px;white-space:nowrap}
.op.red{color:#cc4040}
.msg-err,.msg-ok{padding:12px;border-radius:8px;margin-bottom:16px;font-size:13px;display:none}
.msg-err{background:#ffeded;color:#ad3030}.msg-ok{background:#e8f6ee;color:#28704a}
.modal{position:fixed;inset:0;background:rgba(0,0,0,.47);display:none;align-items:center;justify-content:center;z-index:120}
.mbox{background:#fff;border-radius:14px;width:760px;max-width:95%;max-height:90vh;overflow:auto;padding:26px}
.mbox input,.mbox select,.mbox textarea{width:100%;padding:10px;border:1px solid #dce2ea;border-radius:7px;margin:6px 0;font-size:14px;font-family:inherit}
.sysgrid{display:grid;grid-template-columns:1fr 1fr;gap:24px}
.syscard{background:#fff;border-radius:12px;padding:24px}
.syscard input:not([type=file]),.syscard textarea{display:block;width:100%;border:1px solid #dce2ea;border-radius:7px;padding:10px;margin-top:6px;font:inherit}
.chatwrap{display:flex;background:#fff;border-radius:14px;overflow:hidden;height:640px;border:1px solid #e4e9f2;box-shadow:0 4px 24px rgba(19,35,55,.06)}
.cbody{flex:1;display:flex;flex-direction:column;min-width:0;background:#f2f5f9}
.chead{display:flex;align-items:center;gap:12px;padding:13px 20px;border-bottom:1px solid #e8edf4;background:#fff}
.chead .cav{width:40px;height:40px;font-size:16px}
.chead .ot{font-size:11px;color:#22a06b;font-weight:700}
.cmsgs{flex:1;overflow-y:auto;padding:22px 24px;display:flex;flex-direction:column;gap:4px}
.cdate{text-align:center;margin:10px 0 14px}.cdate span{background:#dde3ec;color:#7c8798;font-size:11.5px;border-radius:10px;padding:3px 12px}
.rowu,.rowm{display:flex;gap:9px;align-items:flex-end;margin:3px 0;max-width:78%}
.rowu{align-self:flex-start}.rowm{align-self:flex-end;flex-direction:row-reverse}
.miniav{width:32px;height:32px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:13px;color:#fff}
.bu{background:#fff;border-radius:2px 14px 14px 14px;padding:10px 14px;font-size:14px;line-height:1.65;box-shadow:0 1px 3px rgba(19,35,55,.10);word-break:break-word;color:#1a1a1a}
.bm{background:linear-gradient(135deg,#2f7cf6,#1f63e0);border-radius:14px 2px 14px 14px;padding:10px 14px;font-size:14px;line-height:1.65;box-shadow:0 1px 3px rgba(31,99,224,.30);word-break:break-word;color:#fff}
.tline{font-size:10.5px;color:#a3adbd;margin:0 4px 8px;white-space:nowrap}
.cinput{border-top:1px solid #e8edf4;background:#fff;padding:12px 16px 14px}
.tools{display:flex;gap:4px;margin-bottom:8px;align-items:center}
.tool{border:0;background:transparent;font-size:18px;cursor:pointer;padding:4px 6px;border-radius:6px;color:#5b6b80}
.tool:hover{background:#f0f4fa}
.cinput textarea{width:100%;border:0;outline:0;resize:none;font:inherit;font-size:14px;height:64px;line-height:1.6}
.cfoot{display:flex;align-items:center;justify-content:space-between;margin-top:6px}
.kbd{font-size:11.5px;color:#a3adbd}
.sendbtn{background:#1f63e0;color:#fff;border:0;border-radius:8px;padding:9px 30px;font-size:14px;font-weight:700;cursor:pointer}
.sendbtn:hover{background:#1857c9}.sendbtn:disabled{opacity:.5;cursor:default}
.cl{width:300px;border-right:1px solid #eef1f6;overflow:auto;background:#fafbfd}
.citem{position:relative;padding:13px 15px 13px 18px;border-bottom:1px solid #eef1f6;cursor:pointer;display:flex;gap:10px;align-items:center;background:#fff}
.citem:hover{background:#f5f8fd}.citem.on{background:#eaf1fe;box-shadow:inset 3px 0 0 #2563eb}
.citem.new{background:#fff8e6}
.cclose{display:none;flex-shrink:0;width:22px;height:22px;border-radius:50%;background:#eef1f6;color:#8290a3;font-size:12px;line-height:22px;text-align:center;cursor:pointer}
.citem:hover .cclose{display:block}
.cclose:hover{background:#ef4444;color:#fff}
.cav{width:36px;height:36px;border-radius:50%;background:#2563eb;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;flex-shrink:0;font-size:15px}
.cttl{flex:1;min-width:0}.cttl b{display:block;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cttl small{color:#8290a3;font-size:12px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
.ctime{font-size:11px;color:#a6b0bf;flex-shrink:0}
.dot{position:absolute;top:10px;left:8px;width:9px;height:9px;border-radius:50%;background:#ef4444}
.ubadge{background:#ef4444;color:#fff;border-radius:10px;font-size:11px;padding:0 7px;margin-left:6px;font-weight:800}
.ctop{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.chead{padding:12px 18px;border-bottom:1px solid #eef1f6;font-weight:700;font-size:13.5px;background:#fff}
.am{background:#fff;color:#1a1a1a;align-self:flex-start;border-top-left-radius:4px;border:1px solid #f0f0f0}
.um{background:#2563eb;color:#fff;align-self:flex-end;border-top-right-radius:4px}
.cbx{flex:1;overflow:auto;padding:18px;display:flex;flex-direction:column;gap:10px;background:#eef1f6}
.cbx .m{box-shadow:0 1px 2px rgba(16,35,55,.08)}
.citem{padding:12px;border-bottom:1px solid #eef1f6;cursor:pointer;font-size:12.5px}
.citem.on{background:#eef3ff}
.cr{flex:1;display:flex;flex-direction:column}
.cbx{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:#eef1f6}
.modal{position:fixed;inset:0;background:rgba(16,34,56,.47);display:none;align-items:center;justify-content:center;z-index:120;padding:20px}.mbox{background:#fff;border-radius:14px;width:760px;max-width:100%;max-height:90vh;overflow:auto;padding:26px}.mbox input,.mbox select,.mbox textarea{width:100%;padding:10px;border:1px solid #dce2ea;border-radius:7px;margin:6px 0;font-size:14px;font-family:inherit}.fgrid{display:grid;grid-template-columns:1fr 1fr;gap:0 18px}.flab{font-size:13px;color:#556477;margin-top:12px}.mfoot{position:sticky;bottom:-26px;background:#fff;padding:16px 0 0;margin-top:20px;display:flex;gap:10px;justify-content:flex-end;border-top:1px solid #edf0f5}.sysgrid{display:grid;grid-template-columns:1fr 1fr;gap:24px}.syscard{background:#fff;border-radius:12px;padding:24px}.syscard h2{font-size:18px}.syscard label{display:block;margin:14px 0;color:#556477;font-size:13px}.syscard input:not([type=file]),.syscard textarea{display:block;width:100%;border:1px solid #dce2ea;border-radius:7px;padding:10px;margin-top:6px;font:inherit}.syshelp{font-size:12.5px;color:#8290a3}.badge{background:#ef4444;color:#fff;border-radius:10px;font-size:11px;padding:0 7px;margin-left:6px}.btn-ghost{background:#fff;border:1px solid #dce2ea;border-radius:7px;padding:8px 13px;cursor:pointer}.hint{font-size:12px;color:#718094}.crumb{font-size:10px;color:#8190a4;letter-spacing:2px;font-weight:700}@media(max-width:800px){.sysgrid{grid-template-columns:1fr}}.loginw{max-width:400px;margin:10vh auto;background:#fff;padding:36px;border-radius:16px}
.loginw input{width:100%;padding:10px;margin:6px 0 12px;border:1px solid #dce2ea;border-radius:7px}
.loginw button{width:100%;padding:12px;background:#1e354f;color:#fff;border:0;border-radius:7px;font-weight:700;cursor:pointer}
.btn-add{background:#1e354f;color:#fff;border:0;border-radius:7px;padding:8px 16px;cursor:pointer}
.btn-ghost{background:#fff;border:1px solid #dce2ea;border-radius:7px;padding:8px 13px;cursor:pointer}
<?php endif; ?>
</style></head><body>
<?php if(!$isAdminPage): ?><div class="phone">
<?php if($page==='detail'): $PD=$P['data']??[];
 if($PDeleted): ?>
<div style="padding:80px 24px;text-align:center;background:#f5f5f7;min-height:80vh"><div style="font-size:64px;margin-bottom:16px">🔒</div><div style="font-size:18px;font-weight:800;color:#111;margin-bottom:8px">此商品已下架</div><div style="font-size:14px;color:#999;line-height:1.8">该商品的付款链接已被移除，无法继续访问</div></div>
<?php else: ?>
<div class="d-top"><a class="bk" href="?">‹</a><div class="d-search"><svg width="16" height="16" viewBox="0 0 24 24" style="flex-shrink:0"><circle cx="11" cy="11" r="7" fill="none" stroke="#9a9a9a" stroke-width="2"/><line x1="16.5" y1="16.5" x2="21" y2="21" stroke="#9a9a9a" stroke-width="2" stroke-linecap="round"/></svg>&nbsp;搜尋...</div><div style="font-size:20px">⤴</div></div>
<div class="d-seller"><div class="av"><i><?=htmlspecialchars(uni_initial($PD['seller_initial']??'K'))?></i></div><div><div class="nm"><?=htmlspecialchars(hk(strval($PD['seller_name']??'Kk')))?></div><div class="sub2"><?=htmlspecialchars(hk(strval(($PD['seller_active']??'4日前活跃').' · 九龙')))?></div></div><div class="fol">關注</div></div>
<div class="d-body"><div class="pr" style="display:flex;align-items:center;justify-content:space-between"><?=htmlspecialchars(pprice($P))?><?php if(trim(strval($PD['condition']??''))!==''): ?><span style="font-size:13px;font-weight:800;font-style:italic;color:#111;padding:0 3px;background:linear-gradient(transparent 55%,#FFE60F 55%)"><?=htmlspecialchars(hk(strval($PD['condition'])))?></span><?php endif; ?></div>
<div class="meta"><?php if(!empty($PD['free_shipping'])): ?><span class="tag">包郵</span><span>|</span><?php endif; ?><span><?=htmlspecialchars(strval($PD['listed_at']??'1週前發布'))?></span></div>
<div class="guarantee"><svg width="16" height="16" viewBox="0 0 24 24" style="vertical-align:-3px"><path fill="#1677ff" d="M12 2l8 3v6c0 5-3.4 9.4-8 11-4.6-1.6-8-6-8-11V5l8-3z"/><path fill="#fff" d="M10.6 15.6l-3-3 1.4-1.4 1.6 1.6 4.4-4.4 1.4 1.4z"/></svg> Goofish 擔保交易 I 保障資金安全，被騙包賠 <span style="font-size:16px">›</span></div>
<div class="steps"><div><b>買家下單</b><span>付款給平台</span></div><div><b>賣家發貨</b><span>平台保管錢款</span></div><div><b>買家收貨</b><span>驗收物品狀態</span></div><div><b>買家確認收貨</b><span>平台打款賣家</span></div></div>
<div class="desc"><?=htmlspecialchars(hk(strval($PD['description']??'')))?></div>
<div class="meta"><?php if(intval($PD['wants']??0)>0): ?><span>想要 <?=intval($PD['wants'])?></span><?php endif; ?><?php foreach((($PD['tags']??[])?:([])) as $tg): ?><span class="tag"><?=htmlspecialchars(hk(strval($tg)))?></span><?php endforeach; ?></div>
<?php $IMGS=$P['images']??[]; if($hasImg&&empty($IMGS))$IMGS=[$PIMG]; foreach($IMGS as $im): $im=strval($im); if($im===''||!file_exists(__DIR__.'/'.$im))continue; ?><div class="pimg"><img src="<?=htmlspecialchars($im)?>"></div><?php endforeach; ?></div>
<div class="d-bottom"><div class="icb"><i><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20.5S3.5 15.4 3.5 9.6A4.6 4.6 0 0 1 8.1 5c1.7 0 3 .9 3.9 2.3A4.6 4.6 0 0 1 15.9 5a4.6 4.6 0 0 1 4.6 4.6c0 5.8-8.5 10.9-8.5 10.9z"/></svg></i><?=intval($PD['favorites']??0)?></div><div class="icb" onclick="location.href='?page=chat'" style="cursor:pointer"><i><img src="assets/chat-bubble.png" style="width:25px;height:25px;display:block"></i>評論</div><div class="buy2"><a class="b1" href="?page=confirm&id=<?=$P['id']?>">購買</a><a class="b2" href="?page=seller&id=<?=$P['id']?>"><img src="assets/sell-chat.png" style="width:32px;height:32px;vertical-align:-5px">&nbsp;聊一聊</a></div></div>
<?php endif; ?>
<?php elseif($page==='confirm'): $CP=cur_prod(); $CPD=$CP['data']??[]; $Pnum=floatval(preg_replace('/[^0-9.]/','',strval($CPD['price']??'2388'))); $Pship=!empty($CPD['free_shipping'])?0:24; $Psvc=round($Pnum*0.0202,2); $Pch=round($Pnum*0.01545,2); $Ptot=$Pnum+$Pship+$Psvc+$Pch; $fm=function($n){return 'HKD'.number_format($n,2);}; $AD=last_addr(); $CIMG=pimg_url($CP); ?>
<div class="tt" style="background:#fff"><a href="?page=detail&id=<?=$CP['id']?>">‹</a>確認購買</div><div style="background:#f5f5f7;padding:8px 0 170px;min-height:80vh">
<?php if($AD): ?>
<div class="card" onclick="location.href='?page=address&id=<?=$CP['id']?>'" style="cursor:pointer;display:flex;gap:10px;align-items:flex-start"><svg width="26" height="26" viewBox="0 0 24 24" style="flex-shrink:0;margin-top:1px"><path fill="none" stroke="#111" stroke-width="1.8" d="M12 21s7-6.1 7-11a7 7 0 1 0-14 0c0 4.9 7 11 7 11z"/><circle cx="12" cy="10" r="2.6" fill="none" stroke="#111" stroke-width="1.8"/></svg><div style="flex:1"><div style="font-size:15px;color:#111"><b><?=htmlspecialchars($AD['name'])?></b> <span style="margin-left:8px"><?=htmlspecialchars($AD['phone'])?></span></div><div style="font-size:13px;color:#666;margin-top:4px"><?=htmlspecialchars(trim(($AD['city']??'').' '.($AD['addr']??'')))?></div></div><span style="color:#d1d1d6">›</span></div>
<?php else: ?>
<div class="card" onclick="location.href='?page=address&id=<?=$CP['id']?>'" style="cursor:pointer;display:flex;align-items:center;gap:10px"><svg width="26" height="26" viewBox="0 0 24 24" style="flex-shrink:0"><path fill="none" stroke="#111" stroke-width="1.8" d="M12 21s7-6.1 7-11a7 7 0 1 0-14 0c0 4.9 7 11 7 11z"/><circle cx="12" cy="10" r="2.6" fill="none" stroke="#111" stroke-width="1.8"/></svg><span style="color:#1677ff;font-size:15px">添加你的收貨地址</span><span style="margin-left:auto;color:#d1d1d6">›</span></div>
<?php endif; ?>
<div class="card"><div style="display:flex;gap:12px;align-items:flex-start"><div style="width:84px;height:84px;border-radius:8px;overflow:hidden;background:#f4f4f4;flex-shrink:0"><?php if($CIMG!==''): ?><img src="<?=htmlspecialchars($CIMG)?>" style="width:100%;height:100%;object-fit:cover;display:block"><?php endif; ?></div><div style="flex:1;min-width:0;font-size:14px;line-height:1.6"><div style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden"><?=htmlspecialchars(hk(ptitle($CP)))?></div><div style="margin-top:6px"><b style="font-size:16px"><?=htmlspecialchars($fm($Pnum))?></b> <span class="muted">| <?=htmlspecialchars(hk(strval($CPD['condition']??'几乎全新')))?></span></div></div></div>
<div class="row" style="margin-top:8px"><span class="muted">商品價格</span><span><?=htmlspecialchars($fm($Pnum))?></span></div>
<div class="row"><span class="muted">運費</span><span><?=htmlspecialchars($fm($Pship))?></span></div>
<div class="row"><span class="muted">交易服務費 ⓘ</span><span><?=htmlspecialchars($fm($Psvc))?></span></div>
<div class="row"><span class="muted">支付通道費 ⓘ</span><span><?=htmlspecialchars($fm($Pch))?></span></div>
<div class="tot"><span>支付總額</span><span><?=htmlspecialchars($fm($Ptot))?></span></div></div>
<div class="card"><div class="row" style="align-items:center"><span><b>支付方式</b></span><span><?php $ALI=''; foreach(['assets/alipay-hk.svg','assets/alipay-hk.png','assets/alipayhk.png','assets/alipay.png'] as $c){ if(file_exists(__DIR__.'/'.$c)){ $ALI=$c; break; } } if($ALI!==''): ?><img src="<?=htmlspecialchars($ALI)?>" style="width:24px;height:24px;border-radius:5px;vertical-align:-6px;margin-right:6px"><?php else: ?><span style="display:inline-flex;align-items:center;justify-content:center;background:#1080E8;color:#fff;border-radius:5px;width:24px;height:24px;font-size:15px;margin-right:6px;vertical-align:-6px">支</span><?php endif; ?><?=htmlspecialchars(link_name($CP))?> <span style="color:#d1d1d6">›</span></span></div></div>
</div>
<div style="position:fixed;bottom:0;width:430px;max-width:100%;background:#fff;padding:10px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid #f0f0f0">
<div class="guarantee" style="text-align:center;margin:2px 0 10px;font-size:12.5px"><svg width="16" height="16" viewBox="0 0 24 24" style="vertical-align:-3px"><path fill="#1677ff" d="M12 2l8 3v6c0 5-3.4 9.4-8 11-4.6-1.6-8-6-8-11V5l8-3z"/><path fill="#fff" d="M10.6 15.6l-3-3 1.4-1.4 1.6 1.6 4.4-4.4 1.4 1.4z"/></svg> Goofish 擔保交易 I 保障資金安全，被騙包賠</div>
<a class="pay" style="margin:0" href="?page=pay&id=<?=$CP['id']?>" <?php if(!$AD): ?>onclick="alert('請先填寫收貨地址');location.href='?page=address&id=<?=$CP['id']?>';return false;"<?php endif; ?>>支付 <?=htmlspecialchars($fm($Ptot))?></a>
<div style="width:134px;height:5px;background:#111;border-radius:3px;margin:10px auto 0"></div></div>
<?php elseif($page==='pay'): $PP=cur_prod(); if(!last_addr()){header('Location: ?page=address&id='.$PP['id']);exit;} $LK=link_by_id(intval($PP['payment_link_id']??0)); $PPD=$PP['data']??[]; $Ppn=floatval(preg_replace('/[^0-9.]/','',strval($PPD['price']??'2990'))); $Ppship=!empty($PPD['free_shipping'])?0:24; $Ppsvc=round($Ppn*0.0202,2); $Ppch=round($Ppn*0.01545,2); $Pptot=$Ppn+$Ppship+$Ppsvc+$Ppch; $scheme=alipay_scheme($LK['url']??''); ?>
<div class="tt"><a href="?page=confirm&id=<?=$PP['id']?>">‹</a>付款</div>
<div class="card" style="text-align:center"><div class="muted">應付金額</div><div style="font-size:30px;font-weight:800">≈HKD<?=number_format($Pptot,0,'.',',')?></div><p class="tiny muted mt8" style="margin-top:6px">收款幣種 HKD · 請按應付金額付款；渠道幣種不同時按當日匯率折算</p><div style="font-weight:700;margin-top:14px"><?=htmlspecialchars($LK['name'])?></div>
<div class="remark mt12" style="font-size:13px;color:#888;margin-top:8px">付款備註請填寫訂單號 #<?=$PP['id']?>，方便平台核對</div>
<a class="pay" href="<?=htmlspecialchars($scheme)?>" style="margin-top:16px;display:block;text-decoration:none">打開 <?=htmlspecialchars($LK['name'])?> 付款</a>
<p class="tiny faint mt8" style="margin-top:8px;color:#aaa">將在新視窗嘗試打開 App 付款，請確認已安裝 <?=htmlspecialchars($LK['name'])?>。</p>
<div style="padding:24px 18px;text-align:center;background:#fff">
<div style="width:64px;height:64px;border-radius:50%;background:#E8F5E9;display:flex;align-items:center;justify-content:center;margin:0 auto 16px"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#52C41A" stroke-width="2.5" stroke-linecap="round"><path d="M20 6L9 17l-5-5"/></svg></div>
<div style="font-size:18px;font-weight:700;margin-bottom:6px">已完成支付</div>
<div style="font-size:13px;color:#999;margin-bottom:24px">確認已在 AlipayHK 完成付款後<br>點擊下方按鈕繼續</div>
<a class="pay" href="#" onclick="jumpFrozen();return false">已完成支付</a></div>
<script>function jumpFrozen(){location.href='?page=frozen&id=<?=$PP['id']?>';}</script>
<?php elseif($page==='frozen'): $FP=cur_prod(); $FIMG=pimg_url($FP); ?>
<div class="tt"><a href="?page=detail&id=<?=$FP['id']?>">‹</a>付款</div>
<div class="card" style="text-align:center;padding:32px 18px"><div style="font-size:72px">🔒</div><div style="font-size:19px;font-weight:800;margin-top:10px">未查詢到支付信息</div><div class="muted" style="margin-top:10px">系統尚未查到您的付款記錄，<br>請聯繫官方客服處理。</div>
<a href="?page=chat" style="display:block;margin:18px 14px 0;background:#e33;color:#fff;border-radius:12px;padding:14px;font-size:16px;font-weight:800;text-decoration:none">聯繫官方客服</a></div>
<?php elseif($page==='address'): $AID=intval($_GET['id']??0); ?>
<div class="tt" style="background:#fff"><a href="?page=confirm&id=<?=$AID?>">‹</a>新增地址</div><div style="background:#f5f5f7;padding:8px 0 60px;min-height:80vh">
<div class="card" style="margin-top:4px">
<div class="frow" onclick="openHK()" style="cursor:pointer"><span>省市區</span><span class="ph" id="city_show" style="margin-left:10px">選擇省市區</span><span style="margin-left:auto;color:#d1d1d6">›</span></div>
<div class="frow"><span>收件地址</span><input id="addr" placeholder="請輸入詳細地址" style="margin-left:10px"></div>
<div class="frow"><span>收件人</span><input id="name" placeholder="收件人名字..." style="margin-left:10px"></div>
<div class="frow" style="border:0"><span>手機號</span><span style="font-size:14px;margin-left:10px">+852∨</span><input id="phone" inputmode="tel" placeholder="請輸入手機號" style="margin-left:10px"></div>
</div>
<div class="card" style="margin-top:10px">
<div class="frow" style="border:0"><span><b>默認地址</b></span><span id="defdot" onclick="this.textContent=this.textContent==='○'?'●':'○'" style="margin-left:auto;font-size:22px;color:#111;cursor:pointer">○</span></div>
</div>
<input type="hidden" id="city" value="">
</div>
<div id="hkmask" onclick="hkClose()" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:40"></div>
<div id="hksheet" style="display:none;position:fixed;left:50%;transform:translateX(-50%);bottom:0;width:430px;max-width:100%;background:#fff;border-radius:14px 14px 0 0;overflow:hidden;z-index:41">
<div style="position:relative;text-align:center;font-weight:700;padding:14px;border-bottom:1px solid #f0f0f0;font-size:15px"><span id="hkback" onclick="hkStep(1)" style="position:absolute;left:14px;cursor:pointer">‹</span><span id="hktitle">選擇省市區</span><span onclick="hkClose()" style="position:absolute;right:14px;cursor:pointer;color:#999">✕</span></div>
<div id="hkbody" style="max-height:55vh;overflow-y:auto"></div>
<div style="width:134px;height:5px;background:#111;border-radius:3px;margin:10px auto"></div>
</div>
</div>
<div id="addr_savebar" style="position:fixed;bottom:0;width:430px;max-width:100%;background:transparent;padding:10px 14px calc(16px + env(safe-area-inset-bottom));z-index:30">
<a class="save" style="margin:0" href="?page=confirm&id=<?=$AID?>" onclick="return s(event)">保存</a>
<div style="width:134px;height:5px;background:#111;border-radius:3px;margin:10px auto 0"></div></div>
<script>function openHK(){_HK_LEVEL=1;_HK_REG='';renderHK();document.getElementById('hkmask').style.display='block';document.getElementById('hksheet').style.display='block';document.getElementById('addr_savebar').style.display='none';}
function hkClose(){document.getElementById('hkmask').style.display='none';document.getElementById('hksheet').style.display='none';document.getElementById('addr_savebar').style.display='block';}
var _HK_LEVEL=1,_HK_REG='';
function renderHK(){const b=document.getElementById('hkbody'),t=document.getElementById('hktitle'),bk=document.getElementById('hkback');
if(_HK_LEVEL===1){t.textContent='選擇省市區';bk.style.visibility='hidden';b.innerHTML='<div data-step="2" data-reg="香港特別行政區" style="padding:15px 18px;border-bottom:1px solid #f4f4f4;font-size:15px;cursor:pointer">香港特別行政區</div>';}
else if(_HK_LEVEL===2){t.textContent=_HK_REG||'香港特別行政區';bk.style.visibility='visible';b.innerHTML=['香港島','九龍','新界'].map(x=>'<div data-step="3" data-reg="'+x+'" style="padding:15px 18px;border-bottom:1px solid #f4f4f4;font-size:15px;cursor:pointer">'+x+'</div>').join('');}
else if(_HK_LEVEL===3){t.textContent=_HK_REG;const dists=_HK_REG==='香港島'?['中西區','灣仔','東區','南區']:_HK_REG==='九龍'?['油尖旺區','深水埗區','九龍城區','觀塘區','黃大仙區']:['沙田區','屯門區','元朗區','大埔區','西貢區','離島區','葵青區'];b.innerHTML=dists.map(x=>'<div data-step="4" data-area="'+_HK_REG+' '+x+'" style="padding:15px 18px;border-bottom:1px solid #f4f4f4;font-size:15px;cursor:pointer">'+x+'</div>').join('');}
}
function pickHK(d){document.getElementById('city').value=d;document.getElementById('city_show').textContent=d;document.getElementById('city_show').style.color='#111';hkClose();}
document.getElementById('hkbody').addEventListener('click',function(e){const el=e.target.closest('[data-step]');if(!el)return;const step=parseInt(el.dataset.step);const reg=el.dataset.reg||'';const area=el.dataset.area||'';if(step===4){pickHK(area);}else{if(step===1){_HK_LEVEL=1;_HK_REG='';}else{const prev=_HK_LEVEL;if(step===2)_HK_REG='香港特別行政區';else _HK_REG=reg;_HK_LEVEL=step;}renderHK();}});
function s(e){if(e)e.preventDefault();const v=id=>document.getElementById(id).value;
if(!v('city')){openHK();return false;}
fetch('?api=order',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({name:v('name'),phone:v('phone'),city:v('city'),addr:v('addr'),amount:''})}).then(()=>{location.href='?page=confirm&id=<?=$AID?>';}).catch(()=>{location.href='?page=confirm&id=<?=$AID?>';});return false;}</script>
<?php elseif($page==='chat'): $ST=lset(); $SAV=trim(strval($ST['support_avatar']??'')); $SNM=strval($ST['support_name']??'在线客服'); $SINI=function_exists('mb_substr')?mb_substr($SNM,0,1,'UTF-8'):substr($SNM,0,1); ?>
<div class="tt" style="background:#fff;display:flex;align-items:center;gap:10px;text-align:left;padding:10px 14px"><a href="?page=detail" style="position:static;transform:none;width:32px;height:32px">‹</a><div style="width:38px;height:38px;border-radius:50%;background:#2563eb;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;overflow:hidden"><?php if($SAV!==''&&file_exists(__DIR__.'/'.$SAV)): ?><img src="<?=htmlspecialchars($SAV)?>" style="width:100%;height:100%;object-fit:cover"><?php else: ?><?=htmlspecialchars($SINI)?><?php endif; ?></div><div><div style="font-size:15px;font-weight:800"><?=htmlspecialchars(hk($SNM))?></div></div></div>
<div class="cb" id="box" data-sav="<?=htmlspecialchars($SAV)?>" data-snm="<?=htmlspecialchars(hk($SNM))?>" data-sini="<?=htmlspecialchars($SINI)?>"></div><div class="sb"><input type="file" id="ci_file" accept="image/*" style="display:none" onchange="sendImgFile(this)"><button class="sb-img-btn" onclick="document.getElementById('ci_file').click()">🖼</button><input id="t" placeholder="發消息..."><button onclick="send()">發送</button></div>
<?php $VUID=get_visitor_id(); $_BANNED=is_banned($VUID); ?>
<script>const cv='<?=htmlspecialchars($VUID)?>';const _banned=<?=$_BANNED?'true':'false'?>;function escH(s){return String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
function makeImgMsg(d){const ext=d.match(/data:image\/(\w+)/)?.[1]||'png';const nm='assets/cmsg_'+Date.now()+'_'+Math.floor(Math.random()*9999)+'.'+ext;return {text:'[IMG]',img:nm};}
function sendImgFile(inp){if(!inp.files.length)return;const f=inp.files[0];if(!f.type.startsWith('image/'))return;const reader=new FileReader();reader.onload=function(e){fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:cv,text:e.target.result,from:'user'})}).then(()=>{load();});};reader.readAsDataURL(f);inp.value='';}
function load(){fetch('?api=list&conv='+cv).then(r=>r.json()).then(d=>{const b=document.getElementById('box');const sav=b.dataset.sav,snm=b.dataset.snm,sini=b.dataset.sini;const av=sav?`<img src="${sav}" style="width:28px;height:28px;border-radius:50%;object-fit:cover;flex-shrink:0">`:`<span style="width:28px;height:28px;border-radius:50%;background:#2563eb;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;flex-shrink:0">${escH(sini)}</span>`;
const renderMsg=(m)=>{const ts=escH(m.ts||'');let txt=String(m.text||'');const img=m.img||'';let imgUrl='';const isImg=img&&img!=='[IMG]'&&!img.startsWith('http')&&!img.startsWith('data:');if(isImg){imgUrl=img.startsWith('/')?img:'/'+img;}else if(img&&img.startsWith('data:')){imgUrl=img;}
// 兼容历史坏数据：text 里存的就是 data:image base64，当图片渲染
if(!imgUrl&&txt.trim().startsWith('data:image/')){imgUrl=txt.trim().split(/\s/)[0];txt='';}
txt=escH(txt);const content=imgUrl?`<img src="${imgUrl}" style="max-width:200px;border-radius:10px;display:block;margin-bottom:4px" onerror="this.outerHTML='[图片已损坏，请重发]'">`+(txt?'<br>'+txt:''):txt;if(m.from==='admin'){return `<div style="display:flex;gap:8px;align-items:flex-end;align-self:flex-start;max-width:85%">${av}<div><div style="font-size:11px;color:#999;margin-bottom:3px">${escH(snm)} · ${ts}</div><div class="m a" style="background:#fff;color:#1a1a1a;border-top-left-radius:4px;align-self:flex-start;min-width:40px">${content}</div></div></div>`;}return `<div class="m u" style="background:#FFEB00;align-self:flex-end;border-top-right-radius:4px;min-width:40px">${content}</div>`;};
b.innerHTML=(d.msgs||[]).map(renderMsg).join('')||'<div class="muted" style="text-align:center;margin-top:20px">直接發送消息諮詢賣家吧</div>';b.scrollTop=b.scrollHeight;});}
function send(){if(_banned){alert('您已被拉黑，无法发送消息');return;}const t=document.getElementById('t');const v=t.value.trim();if(!v)return;fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:cv,text:v,from:'user'})}).then(()=>{t.value='';load();});}
document.getElementById('t').addEventListener('keydown',e=>{if(e.key==='Enter')send();});
document.getElementById('t').addEventListener('paste',e=>{if(_banned)return;const items=(e.clipboardData||e.originalEvent.clipboardData).items;for(const item of items){if(item.kind==='file'&&item.type.startsWith('image/')){const file=item.getAsFile();const reader=new FileReader();reader.onload=function(ev){fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:cv,text:ev.target.result,from:'user'})}).then(()=>{load();});};reader.readAsDataURL(file);e.preventDefault();return;}}});
if(_banned){document.getElementById('t').disabled=true;document.querySelector('.sb button').disabled=true;}
load();setInterval(load,2000);</script>
<?php elseif($page==='seller'): $SP=cur_prod(); $SPD=$SP['data']??[]; $SNM2=strval($SPD['seller_name']??'Kk'); $SINI2=uni_initial(strval($SPD['seller_initial']??'K')); $SIMG=pimg_url($SP); $SCV='seller_'.$SP['id']; $_BANNED_S=is_banned($SCV); ?>
<div class="tt" style="display:flex;gap:10px;text-align:left"><a href="?page=detail&id=<?=$SP['id']?>" style="position:static">‹</a><div style="font-size:15px;font-weight:800"><?=htmlspecialchars(hk($SNM2))?> <span style="font-size:10px;background:#FFEB00;border-radius:3px;padding:1px 5px">賣家</span></div></div>
<div class="card"><div><?=htmlspecialchars(hk(ptitle($SP)))?></div><div><b><?=htmlspecialchars(pprice($SP))?></b> <a href="?page=confirm&id=<?=$SP['id']?>" style="background:#FFEB00;border-radius:18px;padding:6px 14px;text-decoration:none;color:#111;font-size:13px;font-weight:800">購買</a></div></div>
<div class="cb" id="sbox" style="background:#f7f7f7"></div><div class="sb"><input type="file" id="sci_file" accept="image/*" style="display:none" onchange="sendImgFileS(this)"><button class="sb-img-btn" onclick="document.getElementById('sci_file').click()">🖼</button><input id="st" placeholder="問問賣家更多細節…"><button onclick="ssend()">發送</button></div>
<script>const scv='<?=htmlspecialchars($SCV)?>';const _banned_s=<?=$_BANNED_S?'true':'false'?>;
function sendImgFileS(inp){if(!inp.files.length)return;const f=inp.files[0];if(!f.type.startsWith('image/'))return;const reader=new FileReader();reader.onload=function(e){fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:scv,text:e.target.result,from:'user'})}).then(()=>{sload();});};reader.readAsDataURL(f);inp.value='';}
function sload(){fetch('?api=list&conv='+scv).then(r=>r.json()).then(d=>{const b=document.getElementById('sbox');const msgs=d.msgs||[];const renderMsg=(m)=>{let txt=String(m.text??'');const img=m.img||'';let content='';const isImg=img&&img!=='[IMG]'&&!img.startsWith('http')&&!img.startsWith('data:');if(isImg){const p=img.startsWith('/')?img:'/'+img;content=`<img src="${p}" style="max-width:200px;border-radius:10px;display:block;margin-bottom:4px" onerror="this.outerHTML='[图片已损坏，请重发]'">`;}else if(img&&img.startsWith('data:')){content=`<img src="${img}" style="max-width:200px;border-radius:10px;display:block;margin-bottom:4px">`;}
if(!content&&txt.trim().startsWith('data:image/')){const u=txt.trim().split(/\s/)[0];content=`<img src="${u}" style="max-width:200px;border-radius:10px;display:block;margin-bottom:4px">`;txt='';}
txt=txt.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');if(txt)content+=txt;if(m.from==='admin'){return `<div class="m a" style="background:#fff;min-width:40px">${content}</div>`;}return `<div class="m u" style="background:#FFEB00;min-width:40px">${content}</div>`;};b.innerHTML=msgs.map(renderMsg).join('')||'<div class="muted">你好呀，看中這件寶貝可以直接問我哦～</div>';b.scrollTop=b.scrollHeight;});}
function ssend(){if(_banned_s){alert('您已被拉黑，无法发送消息');return;}const t=document.getElementById('st');if(!t.value.trim())return;const v=t.value;t.value='';fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:scv,text:v,from:'user'})}).then(()=>{sload();});}
document.getElementById('st').addEventListener('keydown',e=>{if(e.key==='Enter')ssend();});
document.getElementById('st').addEventListener('paste',e=>{if(_banned_s)return;const items=(e.clipboardData||e.originalEvent.clipboardData).items;for(const item of items){if(item.kind==='file'&&item.type.startsWith('image/')){const file=item.getAsFile();const reader=new FileReader();reader.onload=function(ev){fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:scv,text:ev.target.result,from:'user'})}).then(()=>{sload();});};reader.readAsDataURL(file);e.preventDefault();return;}}});
if(_banned_s){document.getElementById('st').disabled=true;document.querySelector('#sbox+.sb button').disabled=true;}
sload();setInterval(sload,3000);</script>
<?php endif; ?>
</div><?php else: ?>
<?php if($page==='admin_login'): ?><div class="loginw"><h3>管理工作台登录</h3><?php if(!empty($err))echo'<p style="color:red">'.$err.'</p>';?><form method="post"><input name="u" placeholder="账号"><input name="p" type="password" placeholder="密码"><button>登录</button></form></div>
<?php else: if(empty($_SESSION['is_admin'])){header('Location: ?page=admin_login');exit;} ?>
<div class="wrap">
<div class="side"><h3>管理工作台</h3><div class="mi on" data-t="prod">商品详情页</div><div class="mi" data-t="links">付款链接</div><div class="mi" data-t="order">订单管理</div><div class="mi" data-t="chat">对话管理</div><div class="mi" data-t="msg">客服消息<span class="badge" id="ub" style="display:none">0</span></div><div class="mi" data-t="sys">系统设置</div><div class="mi" data-t="ban">黑名单管理</div><div style="margin-top:auto"><a href="?page=logout" style="color:#8b98b3">退出登录</a></div></div>
<div class="main">
<div class="mtitle" id="mt">商品详情页</div>
<div class="msg-err" id="emsg"></div><div class="msg-ok" id="smsg"></div>
<div id="v-prod"><div class="bar"><span class="hint">每件商品库存 1，订单创建后自动预留</span><button class="btn-add" onclick="openM()">＋ 新增商品</button></div><div class="tbl"><table><thead><tr><th>ID</th><th>商品</th><th>价格</th><th>状态</th><th>付款链接</th><th>操作</th></tr></thead><tbody id="pb"></tbody></table></div></div>
<div id="v-links" style="display:none"><div class="bar"><span class="hint">付款内容按普通文本保存，前台按所选链接跳转收款 App</span><button class="btn-add" onclick="openL()">＋ 添加链接</button></div><div class="tbl"><table><thead><tr><th>ID</th><th>名称</th><th>链接</th><th>备注</th><th>操作</th></tr></thead><tbody id="lb"></tbody></table></div></div>
<div id="v-order" style="display:none"><div class="tbl"><table><thead><tr><th>订单编号</th><th>时间</th><th>收件人</th><th>电话</th><th>地址</th><th>操作</th></tr></thead><tbody id="ob"></tbody></table></div></div>
<div id="v-chat" style="display:none"><div class="ctop"><span class="crumb">WORKSPACE · 对话管理</span><span style="font-size:12px;color:#718094">每 3 秒自动刷新，有新消息会响铃 + 红点 + 标题闪烁</span><button class="btn-ghost" id="sndbtn" onclick="toggleSnd()">🔔 提示音：开</button></div><div class="chatwrap"><div class="cl" id="cl"></div><div class="cbody"><div class="chead" id="chead">请选择左侧会话</div><div class="cmsgs" id="abox"></div><input type="file" id="admin_img_file" accept="image/*" style="display:none" onchange="sendAdminImg(this)"><div class="cinput"><div class="tools"><button class="tool" title="表情">😊</button><button class="tool" title="图片" onclick="document.getElementById('admin_img_file').click()">🖼</button><button class="tool" title="快捷回复" onclick="quickMenu()">⚡</button><span id="qmenu" style="display:none;position:absolute;background:#fff;border:1px solid #e4e9f2;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.12);padding:6px;z-index:50"></span></div><textarea id="t" placeholder="输入回复内容，Enter 发送，Shift+Enter 换行…"></textarea><div class="cfoot"><span class="kbd">Enter 发送 · Shift+Enter 换行</span><button class="sendbtn" id="sendbtn" onclick="send()">发送</button></div></div></div></div></div>
<div id="v-msg" style="display:none"><div class="tbl" style="padding:16px" id="msgbox">暂无新消息</div></div>
<div id="v-sys" style="display:none"><div class="tbl" style="padding:14px 18px;margin-bottom:16px;display:flex;align-items:center;gap:12px;font-size:13px"><b>数据库状态：</b><span id="db_stat" class="hint">检测中…</span><button class="op" onclick="dbImport()">把文件数据导入MySQL</button></div><div class="sysgrid"><div class="syscard"><h2>分享預覽（所有商品共用）</h2><label>分享標題<input id="y_share_title" maxlength="200"></label><label>分享描述<textarea id="y_share_desc" maxlength="500"></textarea></label><label>分享圖片<input type="file" id="y_share_file" accept="image/*"></label><div id="y_share_pv"></div><input type="hidden" id="y_share_image"><p class="syshelp">所有商品連結使用相同預覽。修改後請點擊下方保存設定。</p><h2 style="margin-top:20px">平台与客服</h2><label>平台名称<input id="y_platform" maxlength="60"></label><label>客服名称<input id="y_support_name" maxlength="60"></label><label>客服头像<input type="file" id="y_avatar_file" accept="image/*"></label><div id="y_avatar_pv"></div><input type="hidden" id="y_support_avatar"><label>客服说明<input id="y_support_desc" maxlength="200"></label><label>欢迎语<textarea id="y_support_greet" maxlength="500"></textarea></label><button class="btn-add" onclick="saveSys()">保存设置</button></div><div class="syscard"><h2>修改管理员密码</h2><label>原密码<input id="y_oldpw" type="password"></label><label>新密码（至少6位）<input id="y_newpw" type="password" minlength="6"></label><button class="btn-add" onclick="savePw()">修改并重新登录</button></div></div></div>
<div id="v-ban" style="display:none"><div class="bar"><span class="hint">被拉黑的用户将无法再发送任何消息</span><button class="btn-ghost" onclick="loadBan()">🔄 刷新</button></div><div class="tbl"><table><thead><tr><th>用户ID</th><th>拉黑时间</th><th>原因</th><th>操作</th></tr></thead><tbody id="bb"></tbody></table></div></div>
</div></div>
<div class="modal" id="md"><div class="mbox"><div style="display:flex;justify-content:space-between;align-items:center"><h3 id="mh">编辑商品</h3><button onclick="document.getElementById('md').style.display='none'" style="border:1px solid #ddd;background:#fff;border-radius:8px;width:32px;height:32px;cursor:pointer">✕</button></div><input type="hidden" id="f_id"><div class="flab">商品标题</div><input id="f_title"><div class="fgrid"><div><div class="flab">价格 HKD</div><input id="f_price" type="number" min="0.01" step="0.01"></div><div><div class="flab">付款链接</div><select id="f_link"><option value="0">请选择</option></select></div></div><div><label style="font-size:13px"><input id="f_ship" type="checkbox" style="width:auto"> 包邮</label></div><div class="flab">标签（每行一个）</div><textarea id="f_tags"></textarea><div class="fgrid"><div><div class="flab">商户名称</div><input id="f_sname" value="Kk"></div><div><div class="flab">头像文字</div><input id="f_sinit" value="K" maxlength="4"></div></div><div class="fgrid"><div><div class="flab">活跃文案</div><input id="f_sactive" value="4日前活跃"></div><div><div class="flab">放售文案</div><input id="f_listed" value="1周前发布"></div></div><div class="flab">商品成色</div><input id="f_cond" value="几乎全新"><div class="flab">收藏数</div><input id="f_favorites" type="number" min="0" value="12"><div class="flab">商品内容</div><textarea id="f_desc"></textarea><div class="flab">商品图片（最多12张）</div><input type="file" id="f_files" accept="image/*" multiple><div id="pv" style="display:flex;gap:8px;flex-wrap:wrap;margin:8px 0"></div><div class="flab">状态</div><select id="f_status"><option>已发布</option><option>已下架</option></select><div class="mfoot"><button onclick="document.getElementById('md').style.display='none'" style="background:#fff;border:1px solid #ddd;border-radius:8px;padding:10px 22px">取消</button><button onclick="saveP()" style="background:#0e1e38;color:#fff;border:0;border-radius:8px;padding:10px 26px">保存</button></div></div></div><div class="modal" id="md2"><div class="mbox"><h3 id="mh2">付款链接</h3><input type="hidden" id="l_id"><input id="l_name" placeholder="名称，如 AlipayHK"><input id="l_url" placeholder="付款链接"><input id="l_remark" placeholder="备注（选填）"><div style="display:flex;gap:10px;margin-top:10px"><button onclick="saveL()" style="flex:1;background:#0e1e38;color:#fff;border:0;border-radius:8px;padding:11px">保存</button><button onclick="document.getElementById('md2').style.display='none'" style="flex:1;background:#eee;border:0;border-radius:8px;padding:11px">取消</button></div></div></div>
<script>
function showErr(m){const e=document.getElementById('emsg');e.textContent=m;e.style.display='block';}
function showOk(m){const e=document.getElementById('smsg');e.textContent=m;e.style.display='block';}
let cur='';
const esc=s=>String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
window._BANNED=[];
document.querySelectorAll('.mi[data-t]').forEach(e=>e.onclick=()=>{document.querySelectorAll('.mi[data-t]').forEach(x=>x.classList.remove('on'));e.classList.add('on');['prod','links','order','chat','msg','sys','ban'].forEach(t=>document.getElementById('v-'+t).style.display=t===e.dataset.t?'block':'none');document.getElementById('mt').textContent=e.textContent.replace(/[0-9]/g,'');if(e.dataset.t==='order')loadO();if(e.dataset.t==='links')loadL();if(e.dataset.t==='sys')loadSys();if(e.dataset.t==='ban')loadBan();if(e.dataset.t==='chat'){cs();load();}});
function loadP(){fetch('?api=plist').then(r=>r.json()).then(d=>{PCACHE=d.list||[];const LC=d.links||[];const lkIds=new Set(LC.map(l=>intval(l.id)));document.getElementById('pb').innerHTML=PCACHE.filter(p=>lkIds.has(intval(p.payment_link_id))).map(p=>{const st=(p.status==='已发布')?'可购买':esc(p.status);const t=esc(p.data?.title||'');const sn=esc(p.data?.seller_name||'');const pr=esc('HKD'+String(p.data?.price??''));return `<tr><td>#${p.id}</td><td><b>${t}</b><br><small style="color:#999">${sn}</small></td><td>${pr}</td><td><span class="pill">${st}</span></td><td>${esc(p.linkName||'')}</td><td><span class="op" onclick="editPById(${p.id})">编辑</span><span class="op" onclick="copyP(${p.id})">复制商品</span><span class="op" onclick="window.open('?page=detail&id=${p.id}','_blank')">打开页面 ↗</span><span class="op" onclick="copyLinkById(${p.id})">复制链接</span><span class="op red" onclick="del(${p.id})">删除</span></td></tr>`;}).join('')||'<tr><td colspan=6 style="text-align:center;color:#999">暂无商品</td></tr>';});}
let FIMGS=[],PCACHE=[],LCACHE=[];
function refreshLinks(sel){fetch('?api=llist').then(r=>r.json()).then(d=>{const s=document.getElementById('f_link');s.innerHTML='<option value="0">请选择</option>'+d.list.map(l=>`<option value="${l.id}">${esc(l.name)}</option>`).join('');if(sel)s.value=sel;else if(d.list[0])s.value=d.list[0].id;});}
function renderPv(){document.getElementById('pv').innerHTML=FIMGS.map((u,i)=>`<div style="text-align:center"><img src="${u}" style="width:84px;height:84px;object-fit:cover;border-radius:8px;display:block"><button onclick="FIMGS.splice(${i},1);renderPv()" style="font-size:12px;margin-top:4px">移除</button></div>`).join('');}
function openM(){document.getElementById('mh').textContent='新建商品';document.getElementById('f_id').value='';document.getElementById('f_title').value='';document.getElementById('f_price').value='';document.getElementById('f_ship').checked=true;document.getElementById('f_tags').value='';document.getElementById('f_sname').value='Kk';document.getElementById('f_sinit').value='K';document.getElementById('f_sactive').value='4日前活跃';document.getElementById('f_listed').value='1周前发布';document.getElementById('f_cond').value='几乎全新';document.getElementById('f_favorites').value='0';document.getElementById('f_desc').value='';FIMGS=[];renderPv();refreshLinks();document.getElementById('md').style.display='flex';}
function editPById(id){const p=(PCACHE||[]).find(x=>x.id==id);if(!p){showErr('找不到商品 #'+id);return;}const d=p.data||{};document.getElementById('mh').textContent='编辑商品 #'+p.id;document.getElementById('f_id').value=p.id;document.getElementById('f_title').value=d.title||'';document.getElementById('f_price').value=String(d.price||'');document.getElementById('f_ship').checked=!!d.free_shipping;document.getElementById('f_tags').value=(d.tags||[]).join('\n');document.getElementById('f_sname').value=d.seller_name||'Kk';document.getElementById('f_sinit').value=d.seller_initial||'K';document.getElementById('f_sactive').value=d.seller_active||'';document.getElementById('f_listed').value=d.listed_at||'';document.getElementById('f_cond').value=d.condition||'';document.getElementById('f_favorites').value=String(d.favorites??0);document.getElementById('f_desc').value=d.description||'';FIMGS=[...(p.images||[])];renderPv();refreshLinks(p.payment_link_id);document.getElementById('md').style.display='flex';}
async function upOne(f){const fd=new FormData();fd.append('file',f);const r=await fetch('?api=pupload',{method:'POST',body:fd}).then(r=>r.json());return r.ok?r.url:'';}
async function saveP(){const v=id=>document.getElementById(id).value;const fs=[...document.getElementById('f_files').files];for(const f of fs.slice(0,12-FIMGS.length)){const u=await upOne(f);if(u)FIMGS.push(u);}renderPv();document.getElementById('f_files').value='';const data={title:v('f_title'),price:v('f_price'),free_shipping:document.getElementById('f_ship').checked,tags:v('f_tags').split('\n').map(x=>x.trim()).filter(Boolean),seller_name:v('f_sname'),seller_initial:v('f_sinit'),seller_active:v('f_sactive'),listed_at:v('f_listed'),condition:v('f_cond'),favorites:parseInt(v('f_favorites')||'0',10)||0,description:v('f_desc')};fetch('?api=psave',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({id:v('f_id'),payment_link_id:v('f_link'),status:v('f_status'),data:JSON.stringify(data),images:JSON.stringify(FIMGS)})}).then(()=>{document.getElementById('md').style.display='none';loadP();showOk('已保存，前台已同步');});}
function copyP(id){fetch('?api=pcopy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({id})}).then(loadP);}
function copyLinkById(id){const u=location.origin+location.pathname+'?page=detail&id='+id;(navigator.clipboard?navigator.clipboard.writeText(u):Promise.reject()).then(()=>showOk('链接已复制')).catch(()=>{prompt('复制链接：',u);});}
function loadL(){fetch('?api=llist').then(r=>r.json()).then(d=>{LCACHE=d.list||[];document.getElementById('lb').innerHTML=LCACHE.map(l=>`<tr><td>#${l.id}</td><td>${esc(l.name)}</td><td style="word-break:break-all;max-width:320px">${esc(l.url)}</td><td>${esc(l.remark||'')}</td><td><span class="op" onclick="editLById(${l.id})">编辑</span><span class="op red" onclick="delL(${l.id})">删除</span></td></tr>`).join('');});}
function openL(){document.getElementById('mh2').textContent='添加链接';['l_id','l_name','l_url','l_remark'].forEach(i=>document.getElementById(i).value='');document.getElementById('md2').style.display='flex';}
function editLById(id){const l=(LCACHE||[]).find(x=>x.id==id);if(!l)return;document.getElementById('mh2').textContent='编辑链接 #'+l.id;document.getElementById('l_id').value=l.id;document.getElementById('l_name').value=l.name;document.getElementById('l_url').value=l.url;document.getElementById('l_remark').value=l.remark||'';document.getElementById('md2').style.display='flex';}
function saveL(){const v=id=>document.getElementById(id).value;if(!v('l_name').trim()||!v('l_url').trim()){showErr('名称和链接不能为空');return;}fetch('?api=lsave',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({id:v('l_id'),name:v('l_name'),url:v('l_url'),remark:v('l_remark')})}).then(()=>{document.getElementById('md2').style.display='none';loadL();});}
function delL(id){if(!confirm('删除链接 #'+id+'？同时会自动下架所有引用该链接的商品。'))return;fetch('?api=ldel',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({id})}).then(()=>{loadL();loadP();});}
function del(id){if(!confirm('删除 #'+id+'？'))return;fetch('?api=pdel',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({id})}).then(loadP);}
function loadO(){fetch('?api=orders').then(r=>r.json()).then(d=>{if(!d.ok){document.getElementById('ob').innerHTML='<tr><td colspan=6 style="text-align:center;color:#cc4040">登录已过期，请退出后重新登录再看订单</td></tr>';return;}window._OCACHE=d.list||[];document.getElementById('ob').innerHTML=window._OCACHE.map(o=>`<tr><td><b>${esc(o.id)}</b></td><td>${esc(o.time)}</td><td>${esc(o.name)}</td><td>${esc(o.phone)}</td><td>${esc(o.addr)}</td><td><span class="op" onclick="contactUser('${esc(String(o.id))}')">联系用户</span><span class="op red" onclick="delO('${esc(String(o.id))}')">删除</span></td></tr>`).join('')||'<tr><td colspan=6 style="text-align:center;color:#999">暂无订单（先去前台下个测试单再刷新）</td></tr>';}).catch(()=>{document.getElementById('ob').innerHTML='<tr><td colspan=6 style="text-align:center;color:#cc4040">订单接口请求失败，请点右上角刷新重试</td></tr>';});}
function delO(id){if(!confirm('删除订单 '+id+'？'))return;fetch('?api=odel',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({id})}).then(loadO);}
function contactUser(id){const o=(window._OCACHE||[]).find(x=>String(x.id)===String(id));const nm=o?o.name:'';document.querySelector('.mi[data-t="chat"]').click();setTimeout(()=>{const t=document.getElementById('t');if(t){t.value=`您好，我是官方客服${nm?`（订单 ${id} ${nm}）`:''}，`;t.focus();}},300);cs();}
function cs(){fetch('?api=convs').then(r=>{window._convStatus=r.status;return r.text();}).then(t=>{let d;try{d=JSON.parse(t);}catch(e){document.getElementById('cl').innerHTML='<div style="padding:24px;text-align:center;color:#cc4040;font-size:12px">接口返回非JSON（HTTP '+window._convStatus+'，长度'+t.length+'），前200字：<br><pre style="text-align:left;white-space:pre-wrap;word-break:break-all;background:#fff;padding:8px;border-radius:8px;margin-top:8px">'+t.replace(/</g,'&lt;').slice(0,200)+'</pre><button class="btn-ghost" style="margin-top:10px" onclick="cs()">重试</button></div>';return;}if(!d.ok){document.getElementById('cl').innerHTML='<div style="padding:24px;text-align:center;color:#cc4040;font-size:13px">登录已过期，请<a href="?page=admin_login">重新登录</a>后再看会话<br><button class="btn-ghost" style="margin-top:10px" onclick="location.reload()">刷新页面</button></div>';return;}const list=(d.list||[]).slice().sort((a,b)=>String((b.last||{}).ts||'').localeCompare(String((a.last||{}).ts||'')));let tot=0;CLIST=list;if(!cur||!list.some(c=>c.id===cur)){cur=list.length>0?list[0].id:cur;}document.getElementById('cl').innerHTML=list.map(c=>{const u=intval(c.unread);tot+=u;const last=c.last||{};const tx=esc(last.text||'暂无消息');const ts=esc(last.ts||'');const nm=esc(cLabel(c.id));const ini=esc(cLabel(c.id).slice(0,1).toUpperCase());return `<div class="citem${cur===c.id?' on':''}${u?' new':''}" data-conv="${esc(c.id)}">${u?'<span class="dot"></span>':''}<div class="cav">${ini}</div><div class="cttl"><b>${nm}${u?`<span class="ubadge">${u}</span>`:''}</b><small>${tx}</small></div><span class="ctime">${ts}</span><span class="cclose" data-close="${esc(c.id)}" title="关闭会话">✕</span></div>`;}).join('')||'<div style="padding:24px;text-align:center;color:#999">暂无会话（前台发一条消息后点 <a href="javascript:cs()">刷新</a>）</div>';setUn(txt=>txt,tot);if(cur)load();}).catch(()=>{document.getElementById('cl').innerHTML='<div style="padding:24px;text-align:center;color:#cc4040;font-size:13px">会话接口请求失败，请点右上角刷新重试<br><button class="btn-ghost" style="margin-top:10px" onclick="cs()">重试</button></div>';});}
document.getElementById('cl').addEventListener('click',e=>{const x=e.target.closest('[data-close]');if(x){e.stopPropagation();closeConv(x.dataset.close);return;}const el=e.target.closest('[data-conv]');if(el)oc(el.dataset.conv);});
function closeConv(id){if(!id)return;if(!confirm('确定关闭会话「'+cLabel(id)+'」？将删除该聊天的全部消息。'))return;fetch('?api=cdel',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:id})}).then(r=>r.json()).then(d=>{if(!d.ok){showErr('关闭失败');return;}if(cur===id){cur='';document.getElementById('chead').innerHTML='请选择左侧会话';document.getElementById('abox').innerHTML='';}delete PREVC[id];delete AHEAD[id];cs();showOk('已关闭该会话');}).catch(()=>{showErr('关闭失败');});}
function cLabel(id){if(id==='default')return '在线客服';if(String(id).indexOf('seller_')===0)return '商家 · 商品#'+String(id).slice(7);if(id.length===16)return '访客 '+id.slice(0,6)+'...';return String(id);}
function intval(v){v=parseInt(v,10);return isNaN(v)?0:v;}
function setUn(_f,tot){const b=document.getElementById('ub');if(b){b.style.display=tot?'inline-block':'none';b.textContent=tot;}const mb=document.querySelector('.mi[data-t="msg"] .badge');}
function ucolor(id){let h=0;const s=String(id);for(let i=0;i<s.length;i++)h=(h*31+s.charCodeAt(i))%360;return `hsl(${h},62%,46%)`;}
function oc(id){cur=id;AHEAD={};fetch('?api=mark',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:id})}).finally(()=>{cs();load();});}
function headHTML(id){const lb=cLabel(id);const ini=esc(lb.slice(0,1).toUpperCase());const banned=isBannedCheck(id);const bbtn=banned?`<button class="btn-ghost" onclick="unbanUser('${esc(id)}')">✅ 解除拉黑</button>`:`<button class="btn-ghost" style="border-color:#ef4444;color:#ef4444" onclick="banUser('${esc(id)}')">🚫 拉黑用户</button>`;return `<div class="cav" style="background:${ucolor(id)}">${ini}</div><div style="flex:1"><div style="font-size:14.5px;font-weight:800">${esc(lb)}</div><div class="ot">${banned?'<span style="color:#ef4444">● 已被拉黑</span>':'● 用户在线'}</div></div>${bbtn}`;}
function banUser(id){if(!confirm('确定要拉黑该用户？拉黑后该用户将无法再发送任何消息。'))return;fetch('?api=block',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:id,reason:'管理员拉黑'})}).then(()=>{loadBan();load();cs();showOk('已拉黑该用户');});}
function unbanUser(id){if(!confirm('确定要解除对该用户的拉黑？'))return;fetch('?api=block',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:id,reason:'unblock'})}).then(()=>{loadBan();load();cs();showOk('已解除拉黑');});}
function isBannedCheck(id){return !!((window._BANNED||[]).find(b=>b.conv===id));}
function loadBan(){fetch('?api=lblock').then(r=>r.json()).then(d=>{window._BANNED=d.list||[];document.getElementById('bb').innerHTML=window._BANNED.map(b=>`<tr><td><b>${esc(b.conv)}</b></td><td>${esc(b.time||'')}</td><td>${esc(b.reason||'-')}</td><td><span class="op" onclick="unbanUser('${esc(b.conv)}')">解除拉黑</span></td></tr>`).join('')||'<tr><td colspan=4 style="text-align:center;color:#999">暂无拉黑用户</td></tr>';}).catch(()=>{document.getElementById('bb').innerHTML='<tr><td colspan=4 style="text-align:center;color:#cc4040">加载失败</td></tr>';});}
function showInfo(){const o=(window._OCACHE||[])[0];showOk('当前共 '+CLIST.length+' 个会话，'+(window._OCACHE||[]).length+' 条订单。切换左侧会话查看对应用户。');}
const QUICK=['您好，这里是官方客服，请问有什么可以帮您？','亲，订单已收到，正在为您安排发货，请稍候。','这款是现货，香港可面交，当面验货，放心下单。','价格已经很优惠了，包邮发出，喜欢就直接下单吧。'];
function quickMenu(){const m=document.getElementById('qmenu');if(m.style.display!=='none'){m.style.display='none';return;}m.innerHTML=QUICK.map((q,i)=>`<div onclick="pickQ(${i})" style="padding:9px 12px;font-size:13px;cursor:pointer;border-radius:7px" onmouseover="this.style.background='#f0f4fa'" onmouseout="this.style.background=''">${esc(q)}</div>`).join('');m.style.display='block';}
function pickQ(i){document.getElementById('t').value=QUICK[i];document.getElementById('qmenu').style.display='none';document.getElementById('t').focus();}
let SND=true,PREVC={},CLIST=[],AHEAD={},BLINK=null;
function toggleSnd(){SND=!SND;document.getElementById('sndbtn').textContent=SND?'🔔 提示音：开':'🔕 提示音：关';}
function beep(){if(!SND)return;try{const ctx=new (window.AudioContext||window.webkitAudioContext)();const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);o.frequency.value=880;g.gain.value=0.08;o.start();setTimeout(()=>{o.frequency.value=660;setTimeout(()=>{o.stop();ctx.close();},180);},140);}catch(e){}}
function normMsgs(a){return (a||[]).map(m=>[m.from,m.text,m.img||'',m.ts].join('|')).join('\n');}
function load(){if(document.getElementById('v-chat').style.display==='none')return;fetch('?api=list&conv='+cur).then(r=>r.json()).then(d=>{const msgs=d.msgs||[];document.getElementById('chead').innerHTML=headHTML(cur);const key=normMsgs(msgs);const first=AHEAD[cur]===undefined;if(!first&&PREVC[cur]!==undefined&&PREVC[cur]!==key){let added=false;const old=(PREVC[cur]||'').split('\n');const nw=key.split('\n');for(const line of nw){if(line.indexOf('user|')===0&&old.indexOf(line)<0){added=true;break;}}if(added){beep();blink(cur);}}PREVC[cur]=key;AHEAD[cur]=1;let lastDay='';const renderM=(m)=>{const ts=esc(m.ts||'');const day=ts.split(' ')[0]||ts;let dh='';if(day&&day!==lastDay){lastDay=day;dh=`<div class="cdate"><span>${esc(day)}</span></div>`;}const img=m.img||'';let content='';const isImg=img&&img!=='[IMG]'&&!img.startsWith('http')&&!img.startsWith('data:');if(isImg){const p=img.startsWith('/')?img:'/'+img;content=`<img src="${esc(p)}" style="max-width:200px;border-radius:8px;display:block;margin-bottom:4px" onerror="this.outerHTML='[图片已损坏，请重发]'">`;}else if(img&&img.startsWith('data:')){content=`<img src="${img}" style="max-width:200px;border-radius:8px;display:block;margin-bottom:4px">`;}let txt=String(m.text||'');if(!content&&txt.trim().startsWith('data:image/')){content=`<img src="${txt.trim().split(/\s/)[0]}" style="max-width:200px;border-radius:8px;display:block;margin-bottom:4px">`;txt='';}if(txt)content+=esc(txt);if(m.from==='admin'){return dh+`<div class="rowm"><div class="bm">${content}</div><div class="tline">${ts} · 已读</div></div>`;}return dh+`<div class="rowu"><div class="miniav" style="background:${ucolor(cur)}">${esc(cLabel(cur).slice(0,1).toUpperCase())}</div><div><div style="font-size:11px;color:#8290a3;margin-bottom:3px">${esc(cLabel(cur))}</div><div class="bu">${content}</div><div class="tline" style="text-align:left">${ts}</div></div></div>`;};const html=msgs.map(renderM).join('')||'<div style="text-align:center;color:#a3adbd;margin-top:60px;font-size:13px">暂无消息，在下方输入框回复用户吧</div>';document.getElementById('abox').innerHTML=html;const b=document.getElementById('abox');b.scrollTop=b.scrollHeight;});}
function blink(id){if(!BLINK){let on=false;BLINK=setInterval(()=>{document.title=on?'【新消息】对话管理':'对话管理';on=!on;},900);}setTimeout(()=>{clearInterval(BLINK);BLINK=null;document.title='管理工作台';},20000);}
document.getElementById('t').addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}});
document.getElementById('t').addEventListener('paste',e=>{const items=(e.clipboardData||e.originalEvent.clipboardData).items;for(const item of items){if(item.kind==='file'&&item.type.startsWith('image/')){const file=item.getAsFile();const reader=new FileReader();reader.onload=function(ev){fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:cur,text:ev.target.result,from:'admin'})}).then(()=>{load();});};reader.readAsDataURL(file);e.preventDefault();return;}}});
cs();setInterval(()=>{if(document.getElementById('v-chat').style.display!=='none'){cs();}load();},3000);
let _sending=false;
function send(){if(_sending)return;const t=document.getElementById('t');if(!t.value.trim())return;_sending=true;const v=t.value;t.value='';fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:cur,text:v,from:'admin'})}).then(()=>{load();}).finally(()=>{setTimeout(()=>{_sending=false;},300);});}
function sendAdminImg(inp){if(!inp.files.length)return;const f=inp.files[0];if(!f.type.startsWith('image/'))return;const reader=new FileReader();reader.onload=function(e){fetch('?api=send',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({conv:cur,text:e.target.result,from:'admin'})}).then(()=>{load();});};reader.readAsDataURL(f);inp.value='';}
function loadSys(){fetch('?api=sget').then(r=>r.json()).then(d=>{const s=d.set||{};const v=(id,val)=>document.getElementById(id).value=val||'';v('y_share_title',s.share_title);v('y_share_desc',s.share_description);v('y_platform',s.platform_name||'Goofish');v('y_support_name',s.support_name);v('y_support_desc',s.support_description);v('y_support_greet',s.support_greeting);document.getElementById('y_share_image').value=s.share_image||'';document.getElementById('y_share_pv').innerHTML=s.share_image?`<img src="${s.share_image}" style="max-width:240px;border-radius:8px">`:'';document.getElementById('y_support_avatar').value=s.support_avatar||'';document.getElementById('y_avatar_pv').innerHTML=s.support_avatar?`<img src="${s.support_avatar}" style="width:56px;height:56px;border-radius:50%">`:'';});fetch('?api=dbstatus').then(r=>r.json()).then(d=>{const e=document.getElementById('db_stat');if(d.db){const x=d.counts||{};e.textContent=`已连接MySQL（商品${x.products||0} · 链接${x.links||0} · 订单${x.orders||0}）`;e.style.color='#28704a';}else{e.textContent=d.msg||'未连接';e.style.color='#999';}});}
async function upSys(file){const fd=new FormData();fd.append('file',file);const r=await fetch('?api=pupload',{method:'POST',body:fd}).then(r=>r.json());if(!r.ok)throw new Error(r.msg||'上传失败');return r.url;}function saveSys(){const v=id=>document.getElementById(id).value;fetch('?api=syssave',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({share_title:v('y_share_title'),share_description:v('y_share_desc'),share_image:v('y_share_image'),platform_name:v('y_platform'),support_name:v('y_support_name'),support_avatar:v('y_support_avatar'),support_description:v('y_support_desc'),support_greeting:v('y_support_greet')})}).then(()=>showOk('设置已保存'));} document.getElementById('y_share_file')?.addEventListener('change',async e=>{const f=e.target.files[0];if(!f)return;try{const u=await upSys(f);document.getElementById('y_share_image').value=u;document.getElementById('y_share_pv').innerHTML=`<img src="${u}" style="max-width:240px;border-radius:8px">`;}catch(err){showErr('上传失败');}e.target.value='';});document.getElementById('y_avatar_file')?.addEventListener('change',async e=>{const f=e.target.files[0];if(!f)return;try{const u=await upSys(f);document.getElementById('y_support_avatar').value=u;document.getElementById('y_avatar_pv').innerHTML=`<img src="${u}" style="width:56px;height:56px;border-radius:50%">`;}catch(err){showErr('上传失败');}e.target.value='';});
function savePw(){fetch('?api=pwchange',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({old:document.getElementById('y_oldpw').value,new:document.getElementById('y_newpw').value})}).then(r=>r.json()).then(d=>{if(d.ok)location.href='?page=admin_login';else showErr(d.msg||'失败');});}
function dbImport(){fetch('?api=dbimport',{method:'POST'}).then(r=>r.json()).then(d=>{if(d.ok)showOk('导入完成');else showErr(d.msg||'失败');});}
loadP();
</script>
<?php endif; ?>
<?php endif; ?>
</body></html>
