diff --git a/SDK_2.0/SDK/epayapi.php b/SDK_2.0/SDK/epayapi.php
new file mode 100644
index 0000000..963146b
--- /dev/null
+++ b/SDK_2.0/SDK/epayapi.php
@@ -0,0 +1,59 @@
+
+
+
+
+
+ 正在为您跳转到支付页面,请稍候...
+
+
+
+ $type,
+ "notify_url" => $notify_url,
+ "return_url" => $return_url,
+ "out_trade_no" => $out_trade_no,
+ "name" => $name,
+ "money" => $money,
+);
+
+//建立请求
+$epay = new EpayCore($epay_config);
+$html_text = $epay->pagePay($parameter);
+echo $html_text;
+
+?>
+正在为您跳转到支付页面,请稍候...
+
+
\ No newline at end of file
diff --git a/SDK_2.0/SDK/index.php b/SDK_2.0/SDK/index.php
new file mode 100644
index 0000000..a15a4f4
--- /dev/null
+++ b/SDK_2.0/SDK/index.php
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ 彩虹易支付接口测试
+
+
+
+
+
\ No newline at end of file
diff --git a/SDK_2.0/SDK/lib/EpayCore.class.php b/SDK_2.0/SDK/lib/EpayCore.class.php
new file mode 100644
index 0000000..19f8426
--- /dev/null
+++ b/SDK_2.0/SDK/lib/EpayCore.class.php
@@ -0,0 +1,184 @@
+apiurl = $config['apiurl'];
+ $this->pid = $config['pid'];
+ $this->platform_public_key = $config['platform_public_key'];
+ $this->merchant_private_key = $config['merchant_private_key'];
+ }
+
+ // 发起支付(页面跳转)
+ public function pagePay($param_tmp, $button='正在跳转'){
+ $requrl = $this->apiurl.'api/pay/submit';
+ $param = $this->buildRequestParam($param_tmp);
+
+ $html = '';
+
+ return $html;
+ }
+
+ // 发起支付(获取链接)
+ public function getPayLink($param_tmp){
+ $requrl = $this->apiurl.'api/pay/submit';
+ $param = $this->buildRequestParam($param_tmp);
+ $url = $requrl.'?'.http_build_query($param);
+ return $url;
+ }
+
+ // 发起支付(API接口)
+ public function apiPay($params){
+ return $this->execute('api/pay/create', $params);
+ }
+
+ // 发起API请求
+ public function execute($path, $params){
+ $path = ltrim($path, '/');
+ $requrl = $this->apiurl.$path;
+ $param = $this->buildRequestParam($params);
+ $response = $this->getHttpResponse($requrl, http_build_query($param));
+ $arr = json_decode($response, true);
+ if($arr && $arr['code'] == 0){
+ if(!$this->verify($arr)){
+ throw new \Exception('返回数据验签失败');
+ }
+ return $arr;
+ }else{
+ throw new \Exception($arr ? $arr['msg'] : '请求失败');
+ }
+ }
+
+ // 回调验证
+ public function verify($arr){
+ if(empty($arr) || empty($arr['sign'])) return false;
+
+ if(empty($arr['timestamp']) || abs(time() - $arr['timestamp']) > 300) return false;
+
+ $sign = $arr['sign'];
+
+ return $this->rsaPublicVerify($this->getSignContent($arr), $sign);
+ }
+
+ // 查询订单支付状态
+ public function orderStatus($trade_no){
+ $result = $this->queryOrder($trade_no);
+ if($result && $result['status']==1){
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ // 查询订单
+ public function queryOrder($trade_no){
+ $params = [
+ 'trade_no' => $trade_no,
+ ];
+ return $this->execute('api/pay/query', $params);
+ }
+
+ // 订单退款
+ public function refund($out_refund_no, $trade_no, $money){
+ $params = [
+ 'trade_no' => $trade_no,
+ 'money' => $money,
+ 'out_refund_no' => $out_refund_no,
+ ];
+ return $this->execute('api/pay/refund', $params);
+ }
+
+ private function buildRequestParam($params){
+ $params['pid'] = $this->pid;
+ $params['timestamp'] = time().'';
+ $mysign = $this->getSign($params);
+ $params['sign'] = $mysign;
+ $params['sign_type'] = $this->sign_type;
+ return $params;
+ }
+
+ // 生成签名
+ private function getSign($params){
+ return $this->rsaPrivateSign($this->getSignContent($params));
+ }
+
+ // 获取待签名字符串
+ private function getSignContent($params){
+ ksort($params);
+ $signstr = '';
+ foreach ($params as $k => $v) {
+ if(is_array($v) || $this->isEmpty($v) || $k == 'sign' || $k == 'sign_type') continue;
+ $signstr .= '&' . $k . '=' . $v;
+ }
+ $signstr = substr($signstr, 1);
+ return $signstr;
+ }
+
+ private function isEmpty($value)
+ {
+ return $value === null || trim($value) === '';
+ }
+
+ // 商户私钥签名
+ private function rsaPrivateSign($data){
+ $key = "-----BEGIN PRIVATE KEY-----\n" .
+ wordwrap($this->merchant_private_key, 64, "\n", true) .
+ "\n-----END PRIVATE KEY-----";
+ $privatekey = openssl_get_privatekey($key);
+ if(!$privatekey){
+ throw new \Exception('签名失败,商户私钥错误');
+ }
+ openssl_sign($data, $sign, $privatekey, OPENSSL_ALGO_SHA256);
+ return base64_encode($sign);
+ }
+
+ // 平台公钥验签
+ private function rsaPublicVerify($data, $sign){
+ $key = "-----BEGIN PUBLIC KEY-----\n" .
+ wordwrap($this->platform_public_key, 64, "\n", true) .
+ "\n-----END PUBLIC KEY-----";
+ $publickey = openssl_get_publickey($key);
+ if (!$publickey) {
+ throw new \Exception("验签失败,平台公钥错误");
+ }
+ $result = openssl_verify($data, base64_decode($sign), $publickey, OPENSSL_ALGO_SHA256);
+ return $result === 1;
+ }
+
+ // 请求外部资源
+ private function getHttpResponse($url, $post = false, $timeout = 10){
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+ $httpheader[] = "Accept: */*";
+ $httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
+ $httpheader[] = "Connection: close";
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ if($post){
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
+ }
+ $response = curl_exec($ch);
+ curl_close($ch);
+ return $response;
+ }
+}
diff --git a/SDK_2.0/SDK/lib/epay.config.php b/SDK_2.0/SDK/lib/epay.config.php
new file mode 100644
index 0000000..0b2a500
--- /dev/null
+++ b/SDK_2.0/SDK/lib/epay.config.php
@@ -0,0 +1,19 @@
+ 'http://pay.www.com/',
+
+ //商户ID
+ 'pid' => '1000',
+
+ //平台公钥
+ 'platform_public_key' => 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApHG7SIN16fd9uZfjZunZuAReemVQe5YNxBhbkogsRkZ86xuDVDCmhRXEzw7Ta3tXPnMIFRJFdjOCfFVarqcOLICtBiiZZ7Y4D6aIMhmOSliIJ3qWUnU75Wr2WMTIJ1o2pnPmczQ2YjAAy1DtQCc/qs35j24zuNYZw2WluSdiMckPFgge93RK6cq/Feqfuzq7y+m87x02gxbbTGVf24YH2f7H9qZSKCxRXHQoVIWTlyHULcY3OY+1CVdU2SKlIWHJ31eoPznXBLUo0UB0rNZnYrHG2mIlD2S119UTwZwx9WTG/v7Cb2lHVybjfL5/KLitddfqcLjJsYXh6KhEtsO6CwIDAQAB',
+
+ //商户私钥
+ 'merchant_private_key' => 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBIB1e5lAYtFyXq5I8UIQ6KidYZcWkn0SwVS8Rk0SNZVrvL/UJk6Q1zkJs4pUCykTBS/tTrP2rNPOsK1VO/AQHIzhvAujsv7UK2LptcsuNRPCF5GYxndQnOawAGKNQKsMuNcDzyuyTMbZIBEYSRWIoU3dMz4wWEFso/VdVS4uKTZWZnBOeCDdzDAJ7TwbmaOkT919DfZbXAoMH9n3sG4BMpqQExTDoFY6dq6EPXCWVZgoUfecAgNKSfX5TagSUaAxq4eF5vsUfvj+LFpYIrIssmSVErtZuRXLHWVSEbsNxdDPNuS3BtxWEY7GRPF9RJevtoC5L5LN7Gn+RYCqZNZv7AgMBAAECggEAEA6ZTb11hQzwsrUAM1s5MNkgbsABIDk6BnTMAfMpRC1awyxYhqoDHTnFTYWuTVwvyUW/PtGKnelbdTPSS5x6jRSr0N+GGDgNYF2Wbpkm3Ni6Jubsb7ZrtRED5Y3Vc9j4JTKZXaJaDEJ9+LNSBLWiFi0C7zH5U/O8ElB8CrxL4ZUaZv0JgV9NcDpS5jAtpPSyBLrdhbEheertJiHQU0V+FaaXq8taNcYIA/Xim6+vqcFFtUA3PBBTXHn/NE5uasXi+N+De4IT+dBmirzVSZjviDPr9RSBUi6KPUSXx6eDa26SKeEqJZvBtlASDM+ZC0yhDz0eyV49tMjk7eF5fnCIwQKBgQC2nEiR2t5Q02tHaKesZMRGOwxEyMFQj6viDW+Yffg59Tu6QYuqdR558/zmzWcJFMH3DVQzTXpzPNU9TA3/yT/Q42iKBP70K8O9tJO+gd/jLHLqgw90Wyh2b4FJXXQqVQMkxGBQKRfNi6krWigJNBs8Z8IhczorQHYNbBIUI05poQKBgQC1BRI8zKf+85GuJXTxJ93RXbkOQMUIhT/6eyFTZvCLC9Qqba1/1ouNbtmxNsFFIC+n+rHRN9btKt90m9YFvXD90m3y34M88QjvaQcA1Kng9Q6Xia8DizpVIYGAR/Pfn36BZQeHHVz9te6QJ9hVOgZO3GG62Echd9M/rwOzuU14GwKBgFGtS2Q5khByz9wLuluIYqXLCWzGoninGkksm0qIpXs+7e0cHh0q72u6rtaI7toH983Jn2ym7esXPYWCPAy5dhq3bG23WFXcMVvrpd2i94IDwo6T+lif4VRAAYLQEwJQLezHDREtoCDmo87pL1kWfkwhWJpfkJgB6AuO1/M763mhAoGBAIPEGj9plcwOzndeSp6UL3IMb/1BBmuqWyTgZiTIpMYCKUFtLsMEj/a2vv2xZsQDpsz2vmMV63weHiRKn2L0QABzIZeOPYCpz6A96lwfcT0QBLwn+95vhVmclyCiv5GDDtnviag/poYD3ZDPgDihkR/sabNRZY2mJH6RzfcQJqULAoGALkSkqr0bplhfyAA6bO42l64th4YUqwouTEgp7rE36wQ28THj0a88HLU4CeiCR6LQAEGpKk04Vst97C1Q5ZeD5rc4xKINl8K5HUH8SsdMDq3r22xur2qr4kanW4hf2P/ehOeEKGuhSL+ZWeApvt1c0rqH4MQT1/7qR/dO2MikkMg=',
+];
+
diff --git a/SDK_2.0/SDK/notify_url.php b/SDK_2.0/SDK/notify_url.php
new file mode 100644
index 0000000..4aafe6e
--- /dev/null
+++ b/SDK_2.0/SDK/notify_url.php
@@ -0,0 +1,45 @@
+verify($_GET);
+
+if($verify_result) {//验证成功
+
+ //商户订单号
+ $out_trade_no = $_GET['out_trade_no'];
+
+ //彩虹易支付交易号
+ $trade_no = $_GET['trade_no'];
+
+ //交易状态
+ $trade_status = $_GET['trade_status'];
+
+ //支付方式
+ $type = $_GET['type'];
+
+ //支付金额
+ $money = $_GET['money'];
+
+ if ($_GET['trade_status'] == 'TRADE_SUCCESS') {
+ //判断该笔订单是否在商户网站中已经做过处理
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
+ //如果有做过处理,不执行商户的业务程序
+ }
+
+ //验证成功返回
+ echo "success";
+}
+else {
+ //验证失败
+ echo "fail";
+}
+?>
\ No newline at end of file
diff --git a/SDK_2.0/SDK/query.php b/SDK_2.0/SDK/query.php
new file mode 100644
index 0000000..49d11e7
--- /dev/null
+++ b/SDK_2.0/SDK/query.php
@@ -0,0 +1,17 @@
+queryOrder($trade_no);
+}catch(Exception $e){
+ echo $e->getMessage();
+ exit;
+}
+
+print_r($result);
\ No newline at end of file
diff --git a/SDK_2.0/SDK/refund.php b/SDK_2.0/SDK/refund.php
new file mode 100644
index 0000000..05ce231
--- /dev/null
+++ b/SDK_2.0/SDK/refund.php
@@ -0,0 +1,19 @@
+refund($out_refund_no, $trade_no, $money);
+}catch(Exception $e){
+ echo $e->getMessage();
+ exit;
+}
+
+print_r($result);
\ No newline at end of file
diff --git a/SDK_2.0/SDK/return_url.php b/SDK_2.0/SDK/return_url.php
new file mode 100644
index 0000000..3ae3367
--- /dev/null
+++ b/SDK_2.0/SDK/return_url.php
@@ -0,0 +1,55 @@
+
+
+
+
+
+ 支付返回页面
+
+
+verify($_GET);
+
+if($verify_result) {//验证成功
+
+ //商户订单号
+ $out_trade_no = $_GET['out_trade_no'];
+
+ //支付宝交易号
+ $trade_no = $_GET['trade_no'];
+
+ //交易状态
+ $trade_status = $_GET['trade_status'];
+
+ //支付方式
+ $type = $_GET['type'];
+
+
+ if($_GET['trade_status'] == 'TRADE_SUCCESS') {
+ //判断该笔订单是否在商户网站中已经做过处理
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
+ //如果有做过处理,不执行商户的业务程序
+ }
+ else {
+ echo "trade_status=".$_GET['trade_status'];
+ }
+
+ echo "验证成功
";
+}
+else {
+ //验证失败
+ echo "验证失败
";
+}
+?>
+
+
\ No newline at end of file
diff --git a/demo/frontend/package-lock.json b/demo/frontend/package-lock.json
index e85f856..09cc787 100644
--- a/demo/frontend/package-lock.json
+++ b/demo/frontend/package-lock.json
@@ -21,6 +21,7 @@
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
"sass": "^1.66.1",
+ "terser": "^5.44.1",
"vite": "^4.4.9"
}
},
@@ -531,12 +532,55 @@
"url": "https://github.com/sponsors/kazupon"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher/-/watcher-2.5.1.tgz",
@@ -1036,6 +1080,19 @@
"url": "https://github.com/sponsors/antfu"
}
},
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -1097,6 +1154,13 @@
"node": ">=8"
}
},
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -1176,6 +1240,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/csstype/-/csstype-3.1.3.tgz",
@@ -1965,6 +2036,16 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1974,6 +2055,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/string-width/-/string-width-4.2.3.tgz",
@@ -2000,6 +2092,26 @@
"node": ">=8"
}
},
+ "node_modules/terser": {
+ "version": "5.44.1",
+ "resolved": "https://mirrors.huaweicloud.com/repository/npm/terser/-/terser-5.44.1.tgz",
+ "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/to-regex-range/-/to-regex-range-5.0.1.tgz",
diff --git a/demo/frontend/package.json b/demo/frontend/package.json
index a22cfad..fd8873d 100644
--- a/demo/frontend/package.json
+++ b/demo/frontend/package.json
@@ -10,24 +10,26 @@
"serve": "vite preview"
},
"dependencies": {
- "vue": "^3.3.4",
- "vue-router": "^4.2.4",
- "vue-i18n": "^9.8.0",
- "pinia": "^2.1.6",
+ "@element-plus/icons-vue": "^2.1.0",
"axios": "^1.5.0",
"element-plus": "^2.3.8",
- "@element-plus/icons-vue": "^2.1.0",
- "qrcode": "^1.5.3"
+ "pinia": "^2.1.6",
+ "qrcode": "^1.5.3",
+ "vue": "^3.3.4",
+ "vue-i18n": "^9.8.0",
+ "vue-router": "^4.2.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
- "vite": "^4.4.9",
- "sass": "^1.66.1"
+ "sass": "^1.66.1",
+ "terser": "^5.44.1",
+ "vite": "^4.4.9"
},
- "keywords": ["vue", "frontend", "aigc"],
+ "keywords": [
+ "vue",
+ "frontend",
+ "aigc"
+ ],
"author": "",
"license": "MIT"
}
-
-
-
diff --git a/demo/frontend/src/directives/lazyLoad.js b/demo/frontend/src/directives/lazyLoad.js
new file mode 100644
index 0000000..d83695f
--- /dev/null
+++ b/demo/frontend/src/directives/lazyLoad.js
@@ -0,0 +1,120 @@
+/**
+ * 图片懒加载指令
+ * 使用 Intersection Observer API 实现
+ */
+
+// 默认占位图(1x1透明像素)
+const defaultPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
+
+// 加载中占位图(可选,灰色背景)
+const loadingPlaceholder = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"%3E%3Crect fill="%23333" width="100" height="100"/%3E%3C/svg%3E'
+
+// 创建 Intersection Observer
+let observer = null
+
+const getObserver = () => {
+ if (observer) return observer
+
+ observer = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ const el = entry.target
+ const src = el.dataset.src
+
+ if (src) {
+ // 创建新图片预加载
+ const img = new Image()
+ img.onload = () => {
+ el.src = src
+ el.classList.add('lazy-loaded')
+ el.classList.remove('lazy-loading')
+ }
+ img.onerror = () => {
+ el.classList.add('lazy-error')
+ el.classList.remove('lazy-loading')
+ }
+ img.src = src
+ }
+
+ // 停止观察
+ observer.unobserve(el)
+ }
+ })
+ }, {
+ rootMargin: '100px', // 提前100px开始加载
+ threshold: 0.1
+ })
+
+ return observer
+}
+
+export const lazyLoad = {
+ mounted(el, binding) {
+ const src = binding.value
+
+ if (!src) return
+
+ // 保存真实src
+ el.dataset.src = src
+
+ // 设置占位图
+ el.src = binding.arg === 'loading' ? loadingPlaceholder : defaultPlaceholder
+ el.classList.add('lazy-loading')
+
+ // 开始观察
+ getObserver().observe(el)
+ },
+
+ updated(el, binding) {
+ // 如果src变化,重新加载
+ if (binding.value !== binding.oldValue && binding.value) {
+ el.dataset.src = binding.value
+ el.classList.remove('lazy-loaded', 'lazy-error')
+ el.classList.add('lazy-loading')
+ getObserver().observe(el)
+ }
+ },
+
+ unmounted(el) {
+ if (observer) {
+ observer.unobserve(el)
+ }
+ }
+}
+
+// 视频懒加载指令
+export const lazyVideo = {
+ mounted(el, binding) {
+ const src = binding.value
+
+ if (!src) return
+
+ el.dataset.src = src
+ el.preload = 'none' // 不预加载
+ el.classList.add('lazy-loading')
+
+ const videoObserver = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ el.src = el.dataset.src
+ el.preload = 'metadata' // 只加载元数据
+ el.classList.add('lazy-loaded')
+ el.classList.remove('lazy-loading')
+ videoObserver.unobserve(el)
+ }
+ })
+ }, {
+ rootMargin: '50px',
+ threshold: 0.1
+ })
+
+ videoObserver.observe(el)
+ }
+}
+
+export default {
+ install(app) {
+ app.directive('lazy', lazyLoad)
+ app.directive('lazy-video', lazyVideo)
+ }
+}
diff --git a/demo/frontend/src/locales/en.js b/demo/frontend/src/locales/en.js
index 7376881..84fd24c 100644
--- a/demo/frontend/src/locales/en.js
+++ b/demo/frontend/src/locales/en.js
@@ -481,7 +481,9 @@ export default {
videoFileNotExist: 'Video file may not exist or has been deleted',
retry: 'Retry',
deleteFailedWork: 'Delete This Work',
- deleteFailedWorkConfirm: 'This work\'s video failed to load. Are you sure you want to delete it? This action cannot be undone.'
+ deleteFailedWorkConfirm: 'This work\'s video failed to load. Are you sure you want to delete it? This action cannot be undone.',
+ readyToGenerateVideo: 'Storyboard image loaded, ready to generate video',
+ noDownloadUrl: 'No downloadable file available'
},
subscription: {
diff --git a/demo/frontend/src/locales/zh.js b/demo/frontend/src/locales/zh.js
index 9dea267..798673a 100644
--- a/demo/frontend/src/locales/zh.js
+++ b/demo/frontend/src/locales/zh.js
@@ -495,7 +495,9 @@ export default {
videoFileNotExist: '视频文件可能不存在或已被删除',
retry: '重试',
deleteFailedWork: '删除此作品',
- deleteFailedWorkConfirm: '此作品视频加载失败,确定要删除吗?删除后无法恢复。'
+ deleteFailedWorkConfirm: '此作品视频加载失败,确定要删除吗?删除后无法恢复。',
+ readyToGenerateVideo: '已填充分镜图,可以开始生成视频',
+ noDownloadUrl: '没有可下载的文件'
},
subscription: {
diff --git a/demo/frontend/src/main.js b/demo/frontend/src/main.js
index 0ab42a7..c9d748f 100644
--- a/demo/frontend/src/main.js
+++ b/demo/frontend/src/main.js
@@ -7,6 +7,7 @@ import App from './App.vue'
import router from './router'
import i18n from './locales'
import { useUserStore } from './stores/user'
+import lazyLoadDirective from './directives/lazyLoad'
const app = createApp(App)
@@ -15,6 +16,7 @@ app.use(pinia)
app.use(router)
app.use(i18n)
app.use(ElementPlus)
+app.use(lazyLoadDirective)
console.log('[main.js] i18n 当前语言:', i18n.global.locale.value)
diff --git a/demo/frontend/src/views/ImageToVideo.vue b/demo/frontend/src/views/ImageToVideo.vue
index b0a7011..59fa78c 100644
--- a/demo/frontend/src/views/ImageToVideo.vue
+++ b/demo/frontend/src/views/ImageToVideo.vue
@@ -63,7 +63,7 @@
@@ -287,6 +287,31 @@ onMounted(() => {