index.uts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import Intent from "android.content.Intent";
  2. import Uri from "android.net.Uri";
  3. import Context from "android.content.Context";
  4. import File from "java.io.File";
  5. import FileProvider from "androidx.core.content.FileProvider";
  6. import { ShareWithSystemOptions } from "../interface.uts";
  7. /**
  8. * 分享类型枚举
  9. */
  10. const ShareType = {
  11. TEXT: "text", // 纯文本分享
  12. IMAGE: "image", // 图片分享
  13. VIDEO: "video", // 视频分享
  14. AUDIO: "audio", // 音频分享
  15. FILE: "file", // 文件分享
  16. LINK: "link" // 链接分享
  17. };
  18. /**
  19. * MIME 类型映射
  20. */
  21. const MimeTypes = {
  22. IMAGE: "image/*",
  23. VIDEO: "video/*",
  24. AUDIO: "audio/*",
  25. TEXT: "text/plain",
  26. PDF: "application/pdf",
  27. WORD: "application/msword",
  28. EXCEL: "application/vnd.ms-excel",
  29. PPT: "application/vnd.ms-powerpoint",
  30. ZIP: "application/zip",
  31. DEFAULT: "*/*"
  32. };
  33. /**
  34. * 判断是否为网络 URL
  35. * @param url 地址
  36. * @returns 是否为网络 URL
  37. */
  38. function isNetworkUrl(url: string): boolean {
  39. return url.startsWith("http://") || url.startsWith("https://");
  40. }
  41. /**
  42. * 根据文件路径获取 File 对象
  43. * 按优先级尝试多种路径解析方式
  44. * @param filePath 文件路径
  45. * @returns File 对象或 null
  46. */
  47. function getFileFromPath(filePath: string): File | null {
  48. // 1. 尝试直接路径
  49. let file = new File(filePath);
  50. if (file.exists()) {
  51. return file;
  52. }
  53. // 2. 尝试资源路径
  54. file = new File(UTSAndroid.getResourcePath(filePath));
  55. if (file.exists()) {
  56. return file;
  57. }
  58. // 3. 尝试绝对路径转换
  59. file = new File(UTSAndroid.convert2AbsFullPath(filePath));
  60. if (file.exists()) {
  61. return file;
  62. }
  63. return null;
  64. }
  65. /**
  66. * 根据文件扩展名获取 MIME 类型
  67. * @param filePath 文件路径
  68. * @param defaultType 默认类型
  69. * @returns MIME 类型字符串
  70. */
  71. function getMimeTypeByPath(filePath: string, defaultType: string): string {
  72. const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
  73. if (ext == "") {
  74. return defaultType;
  75. }
  76. // 常见文件类型映射
  77. const mimeMap = {
  78. // 文档类型
  79. pdf: MimeTypes["PDF"],
  80. doc: MimeTypes["WORD"],
  81. docx: MimeTypes["WORD"],
  82. xls: MimeTypes["EXCEL"],
  83. xlsx: MimeTypes["EXCEL"],
  84. ppt: MimeTypes["PPT"],
  85. pptx: MimeTypes["PPT"],
  86. // 压缩包类型
  87. zip: MimeTypes["ZIP"],
  88. rar: "application/x-rar-compressed",
  89. "7z": "application/x-7z-compressed",
  90. tar: "application/x-tar",
  91. gz: "application/gzip"
  92. };
  93. return (mimeMap[ext] as string) ?? defaultType;
  94. }
  95. /**
  96. * 下载网络文件到本地缓存
  97. * @param url 网络地址
  98. * @param success 成功回调,返回本地文件路径
  99. * @param fail 失败回调
  100. */
  101. function downloadNetworkFile(
  102. url: string,
  103. success: (localPath: string) => void,
  104. fail: (error: string) => void
  105. ): void {
  106. uni.downloadFile({
  107. url: url,
  108. success: (res) => {
  109. if (res.statusCode == 200) {
  110. success(res.tempFilePath);
  111. } else {
  112. fail("下载失败,状态码: " + res.statusCode);
  113. }
  114. },
  115. fail: (err) => {
  116. fail("下载失败: " + (err.errMsg ?? "未知错误"));
  117. }
  118. });
  119. }
  120. /**
  121. * 创建文件 Uri
  122. * @param filePath 文件路径(支持本地路径和网络 URL)
  123. * @param success 成功回调
  124. * @param fail 失败回调
  125. */
  126. function createFileUriAsync(
  127. filePath: string,
  128. success: (uri: Uri) => void,
  129. fail: (error: string) => void
  130. ): void {
  131. // 创建文件Uri,支持网络和本地文件。网络文件先下载到本地缓存,再获取Uri。
  132. const handleFileToUri = (localPath: string) => {
  133. const file = getFileFromPath(localPath);
  134. if (file == null) {
  135. fail(`文件不存在: ${localPath}`);
  136. return;
  137. }
  138. const context = UTSAndroid.getAppContext();
  139. if (context == null) {
  140. fail("无法获取App Context");
  141. return;
  142. }
  143. const authority = context.getPackageName() + ".fileprovider";
  144. const uri = FileProvider.getUriForFile(context, authority, file);
  145. success(uri);
  146. };
  147. if (isNetworkUrl(filePath)) {
  148. // 网络路径需先下载,下载完成后处理
  149. downloadNetworkFile(filePath, handleFileToUri, fail);
  150. } else {
  151. // 本地文件直接处理
  152. handleFileToUri(filePath);
  153. }
  154. }
  155. /**
  156. * 创建图片分享 Intent(异步)
  157. * @param url 图片路径(支持本地路径和网络 URL)
  158. * @param title 分享标题
  159. * @param success 成功回调
  160. * @param fail 失败回调
  161. */
  162. function createImageShareIntent(
  163. url: string,
  164. title: string,
  165. success: (intent: Intent) => void,
  166. fail: (error: string) => void
  167. ): void {
  168. if (url == "") {
  169. fail("图片路径不能为空");
  170. return;
  171. }
  172. createFileUriAsync(
  173. url,
  174. (uri: Uri) => {
  175. const intent = new Intent(Intent.ACTION_SEND);
  176. intent.setType(MimeTypes["IMAGE"] as string);
  177. intent.putExtra(Intent.EXTRA_STREAM, uri);
  178. intent.putExtra(Intent.EXTRA_TITLE, title);
  179. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  180. success(intent);
  181. },
  182. (error: string) => {
  183. fail(error);
  184. }
  185. );
  186. }
  187. /**
  188. * 创建视频分享 Intent(异步)
  189. * @param url 视频路径(支持本地路径和网络 URL)
  190. * @param title 分享标题
  191. * @param success 成功回调
  192. * @param fail 失败回调
  193. */
  194. function createVideoShareIntent(
  195. url: string,
  196. title: string,
  197. success: (intent: Intent) => void,
  198. fail: (error: string) => void
  199. ): void {
  200. if (url == "") {
  201. fail("视频路径不能为空");
  202. return;
  203. }
  204. createFileUriAsync(
  205. url,
  206. (uri: Uri) => {
  207. const intent = new Intent(Intent.ACTION_SEND);
  208. intent.setType(MimeTypes["VIDEO"] as string);
  209. intent.putExtra(Intent.EXTRA_STREAM, uri);
  210. intent.putExtra(Intent.EXTRA_TITLE, title);
  211. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  212. success(intent);
  213. },
  214. (error: string) => {
  215. fail(error);
  216. }
  217. );
  218. }
  219. /**
  220. * 创建音频分享 Intent(异步)
  221. * @param url 音频路径(支持本地路径和网络 URL)
  222. * @param title 分享标题
  223. * @param success 成功回调
  224. * @param fail 失败回调
  225. */
  226. function createAudioShareIntent(
  227. url: string,
  228. title: string,
  229. success: (intent: Intent) => void,
  230. fail: (error: string) => void
  231. ): void {
  232. if (url == "") {
  233. fail("音频路径不能为空");
  234. return;
  235. }
  236. createFileUriAsync(
  237. url,
  238. (uri: Uri) => {
  239. const intent = new Intent(Intent.ACTION_SEND);
  240. intent.setType(MimeTypes["AUDIO"] as string);
  241. intent.putExtra(Intent.EXTRA_STREAM, uri);
  242. intent.putExtra(Intent.EXTRA_TITLE, title);
  243. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  244. success(intent);
  245. },
  246. (error: string) => {
  247. fail(error);
  248. }
  249. );
  250. }
  251. /**
  252. * 创建文件分享 Intent(异步)
  253. * @param filePath 文件路径(支持本地路径和网络 URL)
  254. * @param title 分享标题
  255. * @param success 成功回调
  256. * @param fail 失败回调
  257. */
  258. function createFileShareIntent(
  259. filePath: string,
  260. title: string,
  261. success: (intent: Intent) => void,
  262. fail: (error: string) => void
  263. ): void {
  264. if (filePath == "") {
  265. fail("文件路径不能为空");
  266. return;
  267. }
  268. createFileUriAsync(
  269. filePath,
  270. (uri: Uri) => {
  271. // 根据文件扩展名确定 MIME 类型
  272. const mimeType = getMimeTypeByPath(filePath, MimeTypes["DEFAULT"] as string);
  273. const intent = new Intent(Intent.ACTION_SEND);
  274. intent.setType(mimeType);
  275. intent.putExtra(Intent.EXTRA_STREAM, uri);
  276. intent.putExtra(Intent.EXTRA_TITLE, title);
  277. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  278. success(intent);
  279. },
  280. (error: string) => {
  281. fail(error);
  282. }
  283. );
  284. }
  285. /**
  286. * 创建链接分享 Intent
  287. * @param url 链接地址
  288. * @param title 分享标题
  289. * @param summary 分享描述
  290. * @param success 成功回调
  291. * @param fail 失败回调
  292. */
  293. function createLinkShareIntent(
  294. url: string,
  295. title: string,
  296. summary: string,
  297. success: (intent: Intent) => void,
  298. fail: (error: string) => void
  299. ): void {
  300. if (url == "") {
  301. fail("链接地址不能为空");
  302. return;
  303. }
  304. // 组合分享内容:标题 + 描述 + 链接
  305. let content = "";
  306. if (title != "") {
  307. content = title;
  308. }
  309. if (summary != "") {
  310. content = content == "" ? summary : content + "\n" + summary;
  311. }
  312. if (url != "") {
  313. content = content == "" ? url : content + "\n" + url;
  314. }
  315. const intent = new Intent(Intent.ACTION_SEND);
  316. intent.setType(MimeTypes["TEXT"] as string);
  317. intent.putExtra(Intent.EXTRA_TEXT, content);
  318. success(intent);
  319. }
  320. /**
  321. * 创建文本分享 Intent
  322. * @param title 分享标题
  323. * @param summary 分享描述
  324. * @param url 附加链接(可选)
  325. * @param success 成功回调
  326. */
  327. function createTextShareIntent(
  328. title: string,
  329. summary: string,
  330. url: string,
  331. success: (intent: Intent) => void
  332. ): void {
  333. // 组合分享内容
  334. let content = "";
  335. if (title != "") {
  336. content = title;
  337. }
  338. if (summary != "") {
  339. content = content == "" ? summary : content + "\n" + summary;
  340. }
  341. if (url != "") {
  342. content = content == "" ? url : content + "\n" + url;
  343. }
  344. // 如果内容为空,使用默认文本
  345. if (content == "") {
  346. content = "分享内容";
  347. }
  348. const intent = new Intent(Intent.ACTION_SEND);
  349. intent.setType(MimeTypes["TEXT"] as string);
  350. intent.putExtra(Intent.EXTRA_TEXT, content);
  351. success(intent);
  352. }
  353. /**
  354. * 启动分享 Activity
  355. * @param intent 分享 Intent
  356. * @param title 选择器标题
  357. * @param success 成功回调
  358. * @param fail 失败回调
  359. */
  360. function startShareActivity(
  361. intent: Intent,
  362. title: string,
  363. success: () => void,
  364. fail: (error: string) => void
  365. ): void {
  366. const chooserTitle = title != "" ? title : "选择分享方式";
  367. const chooser = Intent.createChooser(intent, chooserTitle);
  368. try {
  369. UTSAndroid.getUniActivity()!.startActivity(chooser);
  370. success();
  371. } catch (e: Exception) {
  372. const errorMsg = e.message ?? "分享失败";
  373. fail(errorMsg);
  374. }
  375. }
  376. /**
  377. * 系统分享功能
  378. * @param options 分享参数
  379. * @param options.type 分享类型: text(文本) | image(图片) | video(视频) | audio(音频) | file(文件) | link(链接)
  380. * @param options.title 分享标题
  381. * @param options.summary 分享描述/内容
  382. * @param options.url 资源路径(图片/视频/音频/文件路径或链接地址,支持本地路径和网络 URL)
  383. * @param options.success 成功回调
  384. * @param options.fail 失败回调
  385. */
  386. export function shareWithSystem(options: ShareWithSystemOptions): void {
  387. const type = options.type;
  388. const title = options.title ?? "";
  389. const summary = options.summary ?? "";
  390. const url = options.url ?? "";
  391. // 成功和失败回调
  392. const onSuccess = (intent: Intent) => {
  393. startShareActivity(
  394. intent,
  395. title,
  396. () => {
  397. options.success?.();
  398. },
  399. (error: string) => {
  400. options.fail?.(error);
  401. }
  402. );
  403. };
  404. const onFail = (error: string) => {
  405. options.fail?.(error);
  406. };
  407. // 根据分享类型创建对应的 Intent
  408. if (type == ShareType["IMAGE"]) {
  409. createImageShareIntent(url, title, onSuccess, onFail);
  410. } else if (type == ShareType["VIDEO"]) {
  411. createVideoShareIntent(url, title, onSuccess, onFail);
  412. } else if (type == ShareType["AUDIO"]) {
  413. createAudioShareIntent(url, title, onSuccess, onFail);
  414. } else if (type == ShareType["FILE"]) {
  415. createFileShareIntent(url, title, onSuccess, onFail);
  416. } else if (type == ShareType["LINK"]) {
  417. createLinkShareIntent(url, title, summary, onSuccess, onFail);
  418. } else {
  419. // 默认为文本分享
  420. createTextShareIntent(title, summary, url, onSuccess);
  421. }
  422. }