ソースを参照

uniapp拍照测试

liuq 2 週間 前
コミット
3f7b287536
3 ファイル変更165 行追加11 行削除
  1. 11 1
      api/main.py
  2. 138 5
      templates/attachment_test/index.html
  3. 16 5
      templates/js/im-sdk.js

+ 11 - 1
api/main.py

@@ -2,7 +2,7 @@ import os
 import time
 import uuid
 
-from flask import Blueprint, render_template, send_from_directory, current_app, redirect, url_for, request, jsonify, session
+from flask import Blueprint, render_template, send_from_directory, current_app, redirect, url_for, request, jsonify, session, abort
 from werkzeug.utils import secure_filename
 
 from api.utils import login_required, load_led_config, get_server_ip
@@ -106,6 +106,16 @@ def attachment_test_page():
     return render_template('attachment_test/index.html', active_page='attachment_test')
 
 
+@main_bp.route('/js/<path:filename>')
+@login_required
+def serve_templates_js(filename):
+    """提供 templates/js 下的脚本(如 im-sdk.js)"""
+    safe = os.path.basename(filename)
+    if safe != 'im-sdk.js':
+        abort(404)
+    return send_from_directory(os.path.join(current_app.root_path, 'templates', 'js'), safe)
+
+
 @main_bp.route('/api/attachment_test/status', methods=['GET'])
 @login_required
 def attachment_test_status():

+ 138 - 5
templates/attachment_test/index.html

@@ -65,12 +65,13 @@
         <p style="color: #666; margin-bottom: 12px; font-size: 0.95em;">每次仅保留一个附件;新上传会替换当前附件。</p>
 
         <div class="attachment-upload-grid">
-            <button type="button" class="btn btn-primary" onclick="document.getElementById('inpCameraPhoto').click()">拍照</button>
-            <button type="button" class="btn btn-primary" onclick="document.getElementById('inpCameraVideo').click()">拍视频</button>
-            <button type="button" class="btn btn-info" onclick="document.getElementById('inpGalleryImage').click()">相册图片</button>
-            <button type="button" class="btn btn-info" onclick="document.getElementById('inpGalleryVideo').click()">相册视频</button>
-            <button type="button" class="btn btn-warning" onclick="document.getElementById('inpAnyFile').click()">选择文件</button>
+            <button type="button" class="btn btn-primary" id="btnCameraPhoto">拍照</button>
+            <button type="button" class="btn btn-primary" id="btnCameraVideo">拍视频</button>
+            <button type="button" class="btn btn-info" id="btnGalleryImage">相册图片</button>
+            <button type="button" class="btn btn-info" id="btnGalleryVideo">相册视频</button>
+            <button type="button" class="btn btn-warning" id="btnAnyFile">选择文件</button>
         </div>
+        <p id="uniappHint" style="display:none;color:#666;font-size:0.9em;margin-bottom:12px;">当前为 UniApp WebView,已使用原生拍照/录像/相册与上传通道。</p>
 
         <input type="file" id="inpCameraPhoto" accept="image/*" capture="environment" style="display:none" />
         <input type="file" id="inpCameraVideo" accept="video/*" capture="environment" style="display:none" />
@@ -89,6 +90,7 @@
 {% endblock %}
 
 {% block scripts %}
+<script src="{{ url_for('main.serve_templates_js', filename='im-sdk.js') }}"></script>
 <script>
 (function () {
     let currentUrl = '';
@@ -97,6 +99,17 @@
     const previewWrap = document.getElementById('previewWrap');
     const previewActions = document.getElementById('previewActions');
     const urlBox = document.getElementById('urlBox');
+    const uniappHint = document.getElementById('uniappHint');
+
+    function useUniNative() {
+        return window.imSDK && typeof imSDK.isUniAppIm === 'function' && imSDK.isUniAppIm();
+    }
+
+    function refreshUniHint() {
+        if (uniappHint && useUniNative()) uniappHint.style.display = 'block';
+    }
+    refreshUniHint();
+    setTimeout(refreshUniHint, 400);
 
     function isImageName(name) {
         return /\.(png|jpe?g|gif|bmp|webp|heic|heif)$/i.test(name || '');
@@ -112,6 +125,59 @@
         });
     }
 
+    function parseNativeUploadResponse(res) {
+        if (res == null) return null;
+        if (typeof res === 'string') {
+            try { return JSON.parse(res); } catch (e) { return null; }
+        }
+        if (typeof res.data === 'string') {
+            try { return JSON.parse(res.data); } catch (e) { return null; }
+        }
+        if (res.data && typeof res.data === 'object') return res.data;
+        return res;
+    }
+
+    function nativeTempPath(res) {
+        if (!res) return '';
+        if (res.tempFilePaths && res.tempFilePaths.length) return res.tempFilePaths[0];
+        if (res.tempFilePath) return res.tempFilePath;
+        if (res.apFilePath) return res.apFilePath;
+        return '';
+    }
+
+    function uploadNativePath(filePath) {
+        if (!filePath) {
+            showMessage('未获取到本地文件路径', 'error');
+            return;
+        }
+        var uploadUrl = window.location.origin + '/api/attachment_test/upload';
+        var timeoutId = setTimeout(function () {
+            showLoading(false);
+            showMessage('上传超时,请检查 App 是否实现 uploadFile 与 Cookie', 'error');
+        }, 120000);
+        showLoading(true);
+        imSDK.uploadFile({
+            url: uploadUrl,
+            filePath: filePath,
+            name: 'file',
+            success: function (res) {
+                clearTimeout(timeoutId);
+                showLoading(false);
+                var data = parseNativeUploadResponse(res);
+                if (data && data.success) {
+                    showMessage(data.message || '上传成功');
+                    renderPreview({
+                        has_file: true,
+                        filename: data.filename,
+                        url: data.url
+                    });
+                } else {
+                    showMessage((data && data.message) || '上传失败', 'error');
+                }
+            }
+        });
+    }
+
     function renderPreview(data) {
         currentUrl = data.url || '';
         currentFilename = data.filename || '';
@@ -200,6 +266,73 @@
     }
     ['inpCameraPhoto', 'inpCameraVideo', 'inpGalleryImage', 'inpGalleryVideo', 'inpAnyFile'].forEach(wireInput);
 
+    function triggerFileInput(id) {
+        var el = document.getElementById(id);
+        if (el) el.click();
+    }
+
+    document.getElementById('btnCameraPhoto').addEventListener('click', function () {
+        if (useUniNative()) {
+            imSDK.chooseImage({
+                count: 1,
+                sourceType: ['camera'],
+                success: function (res) { uploadNativePath(nativeTempPath(res)); }
+            });
+        } else {
+            triggerFileInput('inpCameraPhoto');
+        }
+    });
+    document.getElementById('btnCameraVideo').addEventListener('click', function () {
+        if (useUniNative()) {
+            imSDK.chooseVideo({
+                sourceType: ['camera'],
+                maxDuration: 120,
+                camera: 'back',
+                success: function (res) { uploadNativePath(nativeTempPath(res)); }
+            });
+        } else {
+            triggerFileInput('inpCameraVideo');
+        }
+    });
+    document.getElementById('btnGalleryImage').addEventListener('click', function () {
+        if (useUniNative()) {
+            imSDK.chooseImage({
+                count: 1,
+                sourceType: ['album'],
+                success: function (res) { uploadNativePath(nativeTempPath(res)); }
+            });
+        } else {
+            triggerFileInput('inpGalleryImage');
+        }
+    });
+    document.getElementById('btnGalleryVideo').addEventListener('click', function () {
+        if (useUniNative()) {
+            imSDK.chooseVideo({
+                sourceType: ['album'],
+                maxDuration: 120,
+                camera: 'back',
+                success: function (res) { uploadNativePath(nativeTempPath(res)); }
+            });
+        } else {
+            triggerFileInput('inpGalleryVideo');
+        }
+    });
+    document.getElementById('btnAnyFile').addEventListener('click', function () {
+        if (useUniNative()) {
+            imSDK.chooseFile({
+                count: 1,
+                extension: [],
+                success: function (res) {
+                    var path = nativeTempPath(res);
+                    if (!path && res.tempFiles && res.tempFiles[0]) path = res.tempFiles[0].path;
+                    uploadNativePath(path);
+                }
+            });
+        } else {
+            triggerFileInput('inpAnyFile');
+        }
+    });
+
     document.getElementById('btnShare').addEventListener('click', async function () {
         if (!currentUrl) {
             showMessage('没有可分享的链接', 'error');

+ 16 - 5
templates/js/im-sdk.js

@@ -57,10 +57,24 @@
         checkAndSend();
     }
 
+    /**
+     * 是否在 UniApp WebView 内(用于走 uni 原生拍照/相册/录像,避免 H5 直接调 input file)
+     * 优先级:注入标记 __IN_UNIAPP__ > UA 含 uni-app > 已存在 uni.webView
+     */
+    function isUniAppIm() {
+        if (window.__IN_UNIAPP__ === true) return true;
+        if (/uni-app/i.test(navigator.userAgent || '')) return true;
+        if (window.uni && window.uni.webView) return true;
+        return false;
+    }
+
     // ---------------------------------------------------------
     // 3. 平台标准 API (供子应用开发者主动调用)
     // ---------------------------------------------------------
     window.imSDK = {
+        /** @returns {boolean} */
+        isUniAppIm: isUniAppIm,
+
         /**
          * 调起原生相机拍照或选择相册 (返回本地路径)
          */
@@ -123,11 +137,8 @@
         // 锁定目标:点击了 <input type="file">
         if (target.tagName && target.tagName.toUpperCase() === 'INPUT' && target.type === 'file') {
             
-            // 粗略判断是否在 App 内 (如果不在,则放行,走普通浏览器逻辑)
-            // 建议:在你的 App 壳子 webview 加载时,给 userAgent 拼上一个自定义标识,如 'MyIMPlatform'
-            var isInsideApp = /uni-app/i.test(navigator.userAgent); 
-            
-            if (isInsideApp) {
+            // 在 UniApp WebView 内则拦截,避免系统 file 选择器;普通浏览器放行
+            if (isUniAppIm()) {
                 // 【核心防御】拦截浏览器默认调起系统相册/相机的行为
                 event.preventDefault();
                 event.stopPropagation();