fix(android): 修复PDF/下载/文件选择器,常规设置增加上传日志
This commit is contained in:
parent
426825d475
commit
15dea9c2e5
@ -10,12 +10,12 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.provider.DocumentsContract
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.webkit.DownloadListener
|
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
import android.webkit.PermissionRequest
|
import android.webkit.PermissionRequest
|
||||||
import android.webkit.ValueCallback
|
import android.webkit.ValueCallback
|
||||||
@ -185,10 +185,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
webView.setDownloadListener(DownloadListener { url, _, contentDisposition, _, _ ->
|
|
||||||
startSystemDownload(url, suggestFileName(contentDisposition, url))
|
|
||||||
})
|
|
||||||
|
|
||||||
webView.webChromeClient = object : WebChromeClient() {
|
webView.webChromeClient = object : WebChromeClient() {
|
||||||
override fun onPermissionRequest(request: PermissionRequest) {
|
override fun onPermissionRequest(request: PermissionRequest) {
|
||||||
// 按需放行媒体权限(例如文件上传触发的媒体访问)
|
// 按需放行媒体权限(例如文件上传触发的媒体访问)
|
||||||
@ -203,13 +199,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
this@MainActivity.filePathCallback?.onReceiveValue(null)
|
this@MainActivity.filePathCallback?.onReceiveValue(null)
|
||||||
this@MainActivity.filePathCallback = filePathCallback
|
this@MainActivity.filePathCallback = filePathCallback
|
||||||
|
|
||||||
// 统一使用 ACTION_GET_CONTENT,避免不同 ROM 对 Photo Picker / 相册的兼容差异
|
// 使用 ACTION_OPEN_DOCUMENT 走系统文件管理器(DocumentsUI),
|
||||||
// 导致返回的 URI/文件对象在上传时失效。根据 acceptTypes 设置过滤类型,
|
// 避免 ACTION_GET_CONTENT 在某些 ROM 上唤起相册/Photo Picker 导致返回 URI 失效。
|
||||||
// 图片选择仍只显示图片文件,但走文件管理器而不是系统相册。
|
|
||||||
val acceptTypes = fileChooserParams?.acceptTypes?.filter { it.isNotBlank() }?.takeIf { it.isNotEmpty() }
|
val acceptTypes = fileChooserParams?.acceptTypes?.filter { it.isNotBlank() }?.takeIf { it.isNotEmpty() }
|
||||||
?: arrayListOf("*/*")
|
?: arrayListOf("*/*")
|
||||||
val allowMultiple = fileChooserParams?.mode == FileChooserParams.MODE_OPEN_MULTIPLE
|
val allowMultiple = fileChooserParams?.mode == FileChooserParams.MODE_OPEN_MULTIPLE
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = acceptTypes.first()
|
type = acceptTypes.first()
|
||||||
if (allowMultiple) {
|
if (allowMultiple) {
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@ -770,6 +770,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||||
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/utils": "^0.2.11"
|
"@floating-ui/utils": "^0.2.11"
|
||||||
}
|
}
|
||||||
@ -779,6 +780,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
||||||
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.7.5",
|
"@floating-ui/core": "^1.7.5",
|
||||||
"@floating-ui/utils": "^0.2.11"
|
"@floating-ui/utils": "^0.2.11"
|
||||||
@ -788,7 +790,8 @@
|
|||||||
"version": "0.2.11",
|
"version": "0.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
||||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
@ -6964,7 +6967,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
|
|||||||
@ -75,6 +75,14 @@
|
|||||||
:title="displayName"
|
:title="displayName"
|
||||||
></iframe>
|
></iframe>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="canAndroidPdfPreview" class="sfc-android-pdf-preview" @click="previewPdf">
|
||||||
|
<div class="sfc-android-pdf-placeholder">
|
||||||
|
<span class="sfc-file-icon"></span>
|
||||||
|
<span>PDF 文件(点击预览)</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -94,6 +102,9 @@ const props = defineProps<{
|
|||||||
preview?: string;
|
preview?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// ---- 环境检测 ----
|
||||||
|
const isAndroidApp = typeof window !== 'undefined' && Boolean((window as any).AndroidPdfBridge);
|
||||||
|
|
||||||
// ---- 状态 ----
|
// ---- 状态 ----
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const error = ref('');
|
const error = ref('');
|
||||||
@ -108,9 +119,15 @@ const displayName = computed(() => props.name || props.path.split('/').pop() ||
|
|||||||
|
|
||||||
const canPreview = computed(() => {
|
const canPreview = computed(() => {
|
||||||
if (props.preview === 'off') return false;
|
if (props.preview === 'off') return false;
|
||||||
|
// Android App 内 PDF 不走 iframe 内嵌预览(WebView 不支持),改用原生 PdfPreviewActivity
|
||||||
|
if (isAndroidApp && fileType.value === 'pdf') return false;
|
||||||
return ['text', 'code', 'json', 'csv', 'markdown', 'image', 'pdf'].includes(fileType.value);
|
return ['text', 'code', 'json', 'csv', 'markdown', 'image', 'pdf'].includes(fileType.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canAndroidPdfPreview = computed(() => {
|
||||||
|
return isAndroidApp && fileType.value === 'pdf';
|
||||||
|
});
|
||||||
|
|
||||||
const canCopy = computed(() => ['text', 'code', 'json', 'csv', 'markdown'].includes(fileType.value));
|
const canCopy = computed(() => ['text', 'code', 'json', 'csv', 'markdown'].includes(fileType.value));
|
||||||
|
|
||||||
const metaText = computed(() => {
|
const metaText = computed(() => {
|
||||||
@ -275,6 +292,13 @@ function openFullImage() {
|
|||||||
if (contentUrl.value) window.open(contentUrl.value, '_blank');
|
if (contentUrl.value) window.open(contentUrl.value, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function previewPdf() {
|
||||||
|
const bridge = (window as any).AndroidPdfBridge;
|
||||||
|
if (bridge && typeof bridge.previewPdf === 'function') {
|
||||||
|
bridge.previewPdf(contentUrl.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onImageError() {
|
function onImageError() {
|
||||||
error.value = '图片加载失败';
|
error.value = '图片加载失败';
|
||||||
}
|
}
|
||||||
@ -482,4 +506,31 @@ onMounted(() => {
|
|||||||
border: none;
|
border: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sfc-android-pdf-preview {
|
||||||
|
border-top: 1px solid var(--border-default);
|
||||||
|
background: var(--surface-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sfc-android-pdf-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 24px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sfc-android-pdf-placeholder:hover {
|
||||||
|
background: var(--surface-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sfc-android-pdf-placeholder .sfc-file-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--text-secondary);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -450,6 +450,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-action-row">
|
||||||
|
<span class="settings-row-copy">
|
||||||
|
<span class="settings-row-title">上传诊断日志</span>
|
||||||
|
<span class="settings-row-desc">复现问题后点击,把当前环境信息上传给开发者排查</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="settings-secondary-button"
|
||||||
|
@click="uploadDiagnosticLog"
|
||||||
|
>
|
||||||
|
上传日志
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@ -2259,6 +2272,86 @@ const downloadLatestApp = () => {
|
|||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uploadDiagnosticLog = async () => {
|
||||||
|
try {
|
||||||
|
const w = window as any;
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const screenInfo =
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
? {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
dpr: window.devicePixelRatio
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
const bridgeProbe = (name: string) => {
|
||||||
|
const b = w?.[name];
|
||||||
|
if (!b) return { exists: false };
|
||||||
|
const methods: Record<string, any> = {};
|
||||||
|
[
|
||||||
|
'getAppVersionCode',
|
||||||
|
'getAppVersionName',
|
||||||
|
'onThemeChanged',
|
||||||
|
'previewPdf',
|
||||||
|
'isPdfPreviewSupported',
|
||||||
|
'downloadFile',
|
||||||
|
'isSupported',
|
||||||
|
'isModelReady',
|
||||||
|
'isModelPartial',
|
||||||
|
'startRecording',
|
||||||
|
'stopRecording'
|
||||||
|
].forEach((m) => {
|
||||||
|
try {
|
||||||
|
methods[m] = typeof b[m] === 'function';
|
||||||
|
} catch {
|
||||||
|
methods[m] = 'error';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let versionCode = '';
|
||||||
|
let versionName = '';
|
||||||
|
try {
|
||||||
|
versionCode =
|
||||||
|
typeof b.getAppVersionCode === 'function' ? String(b.getAppVersionCode() || '') : '';
|
||||||
|
versionName =
|
||||||
|
typeof b.getAppVersionName === 'function' ? String(b.getAppVersionName() || '') : '';
|
||||||
|
} catch {}
|
||||||
|
return { exists: true, methods, versionCode, versionName };
|
||||||
|
};
|
||||||
|
const payload = {
|
||||||
|
source: 'diagnostic_log',
|
||||||
|
url: window.location.href,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
screen: screenInfo,
|
||||||
|
urlParams: {
|
||||||
|
app_vc: params.get('app_vc') || '',
|
||||||
|
app_vn: params.get('app_vn') || '',
|
||||||
|
app_shell: params.get('app_shell') || ''
|
||||||
|
},
|
||||||
|
bridges: {
|
||||||
|
AndroidThemeBridge: bridgeProbe('AndroidThemeBridge'),
|
||||||
|
AndroidPdfBridge: bridgeProbe('AndroidPdfBridge'),
|
||||||
|
AndroidDownloadBridge: bridgeProbe('AndroidDownloadBridge'),
|
||||||
|
AndroidVoiceBridge: bridgeProbe('AndroidVoiceBridge')
|
||||||
|
},
|
||||||
|
appCurrentVersionCode: appCurrentVersionCode.value,
|
||||||
|
appCurrentVersionName: appCurrentVersionName.value,
|
||||||
|
appUpdateInfo: appUpdateInfo.value,
|
||||||
|
appUpdateError: appUpdateError.value,
|
||||||
|
appUpdateCheckedAt: appUpdateCheckedAt.value,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
const resp = await fetch('/api/client_debug_log', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
if (!resp.ok) throw new Error(`status ${resp.status}`);
|
||||||
|
alert('诊断日志已上传');
|
||||||
|
} catch (e: any) {
|
||||||
|
alert('上传诊断日志失败:' + (e?.message || String(e)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
hydrateAppVersionFromBridge();
|
hydrateAppVersionFromBridge();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user