AntDesignPro前后端權限按鈕系統實現

目錄

Ant Design Pro 后端接口權限按鈕系統?

系統架構圖

前端實現

權限按鈕組件 (AuthButton.tsx)

權限鉤子 (useAccess.ts)

權限服務 (permission.ts)

產品列表頁面 (ProductList.tsx)

后端接口設計 (Node.js + Express 示例)

權限接口控制器 (permissionController.js)

權限接口路由 (permissionRoutes.js)

數據庫模型 (models.js)

權限系統特點

1. 權限等級體系

2. 權限分類

3. 緩存策略

權限管理最佳實踐

1. 權限按鈕使用示例

2. 權限控制策略

3. 高級權限場景

權限審計與監控


Ant Design Pro 后端接口權限按鈕系統?

下面是一個完整的基于后端接口的權限按鈕實現方案,包含前端頁面、后端接口設計和權限控制系統。

系統架構圖

前端實現

權限按鈕組件 (AuthButton.tsx)

import React from 'react';
import { Button, ButtonProps, Tooltip } from 'antd';
import { useAccess, Access } from 'umi';
import { hasPermission } from '@/services/permission';
import type { PermissionAction } from '@/services/permission';interface AuthButtonProps extends ButtonProps {/** 權限標識 */permission: string;/** 無權限時提示信息 */noPermissionText?: string;/** 是否隱藏無權限按鈕 */hideWhenDisabled?: boolean;/** 是否禁用而非隱藏 */disableWhenNoPermission?: boolean;/** 需要的數據權限上下文 */context?: any;
}const AuthButton: React.FC<AuthButtonProps> = ({permission,noPermissionText = '無操作權限',hideWhenDisabled = true,disableWhenNoPermission = false,context,children,...buttonProps
}) => {const { hasPermission } = useAccess();const hasAuth = hasPermission(permission, context);// 無權限時處理邏輯if (!hasAuth) {// 無權限時需要隱藏按鈕if (hideWhenDisabled) return null;// 無權限但不禁用,添加提示const disabledButton = (<Button {...buttonProps} disabled={disableWhenNoPermission}>{children}</Button>);// 根據配置返回禁用按鈕或帶提示的按鈕return disableWhenNoPermission ? (disabledButton) : (<Tooltip title={noPermissionText}>{disabledButton}</Tooltip>);}// 有權限,返回正常按鈕return (<Button {...buttonProps}>{children}</Button>);
};export default AuthButton;

權限鉤子 (useAccess.ts)

// src/hooks/useAccess.ts
import { useState, useEffect, useCallback } from 'react';
import { getPermissions } from '@/services/permission';type PermissionResult = {hasPermission: (permission: string, context?: any) => boolean;loading: boolean;permissions: string[];refreshPermissions: () => void;
};export default function useAccess(): PermissionResult {const [permissions, setPermissions] = useState<string[]>([]);const [loading, setLoading] = useState(true);// 獲取權限數據const fetchPermissions = useCallback(async () => {setLoading(true);try {const res = await getPermissions();setPermissions(res?.permissions || []);} catch (error) {console.error('獲取權限失敗', error);setPermissions([]);} finally {setLoading(false);}}, []);// 初始化時獲取權限useEffect(() => {fetchPermissions();}, [fetchPermissions]);// 檢查是否有權限const hasPermission = useCallback((permission: string, context?: any): boolean => {// 如果權限列表包含通配符權限if (permissions.includes('*')) return true;// 精確匹配權限if (permissions.includes(permission)) return true;// 基于數據的權限檢查(示例)if (context?.owner === currentUser.id && permissions.includes(`${permission}:own`)) {return true;}return false;}, [permissions]);return {hasPermission,loading,permissions,refreshPermissions: fetchPermissions};
}

權限服務 (permission.ts)

// src/services/permission.ts
import request from 'umi-request';
import type { RequestOptionsInit } from 'umi-request';// 權限類型定義
export type PermissionAction = | 'create' | 'read' | 'update' | 'delete' | 'export' | 'approve' | 'manage' | string;// 權限接口響應格式
export interface PermissionResponse {success: boolean;permissions: string[];
}// 獲取當前用戶權限
export async function getPermissions(options?: RequestOptionsInit
): Promise<PermissionResponse> {return request('/api/permissions', {method: 'GET',...(options || {}),});
}// 檢查特定權限
export async function checkPermission(permission: string, context?: any,options?: RequestOptionsInit
): Promise<{ hasPermission: boolean }> {return request('/api/permissions/check', {method: 'POST',data: { permission, context },...(options || {}),});
}// 權限控制裝飾器(用于頁面組件)
export function withPermissions(WrappedComponent: React.ComponentType) {return (props: any) => {const { loading, permissions } = useAccess();if (loading) {return (<div style={{ textAlign: 'center', padding: 40 }}><Spin tip="權限驗證中..." size="large" /></div>);}if (!permissions.length) {return (<Resultstatus="403"title="403"subTitle="抱歉,您沒有訪問權限,請聯系管理員"/>);}return <WrappedComponent {...props} />;};
}

產品列表頁面 (ProductList.tsx)

import React, { useState } from 'react';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import AuthButton from '@/components/AuthButton';
import { PlusOutlined, EditOutlined, DeleteOutlined, ExportOutlined } from '@ant-design/icons';
import type { ProColumns } from '@ant-design/pro-components';
import { Modal, message } from 'antd';
import ProductForm from './ProductForm';interface Product {id: string;name: string;category: string;price: number;stock: number;owner: string;
}const ProductList: React.FC = () => {const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);const [createModalVisible, setCreateModalVisible] = useState(false);const [editRecord, setEditRecord] = useState<Product | null>(null);const columns: ProColumns<Product>[] = [{title: '產品名稱',dataIndex: 'name',key: 'name',},{title: '類別',dataIndex: 'category',key: 'category',},{title: '價格',dataIndex: 'price',key: 'price',valueType: 'money',},{title: '庫存',dataIndex: 'stock',key: 'stock',renderText: (val: number) => `${val}件`,},{title: '操作',key: 'action',render: (text, record) => (<span><AuthButton type="link" permission="product:update"context={{ owner: record.owner }}onClick={() => handleEdit(record)}><EditOutlined /> 編輯</AuthButton><AuthButton type="link" dangerpermission="product:delete"context={{ owner: record.owner }}noPermissionText="無法刪除非本人創建的產品"onClick={() => handleDelete(record)}><DeleteOutlined /> 刪除</AuthButton></span>),},];const handleCreate = () => {setCreateModalVisible(true);};const handleEdit = (record: Product) => {setEditRecord(record);};const handleDelete = (record: Product) => {Modal.confirm({title: '確認刪除',content: `確定要刪除產品【${record.name}】嗎?`,onOk: () => {// 調用刪除APImessage.success('刪除成功');},});};const handleExport = () => {// 導出操作message.success('導出成功');};const handleBatchDelete = () => {Modal.confirm({title: '批量刪除',content: `確定要刪除選中的${selectedRowKeys.length}個產品嗎?`,onOk: () => {// 調用批量刪除APImessage.success('批量刪除成功');setSelectedRowKeys([]);},});};return (<PageContainer><ProTable<Product>columns={columns}rowKey="id"headerTitle="產品列表"rowSelection={{selectedRowKeys,onChange: setSelectedRowKeys,}}toolBarRender={() => [<AuthButton key="create"type="primary" icon={<PlusOutlined />}permission="product:create"onClick={handleCreate}>新建產品</AuthButton>,<AuthButton key="export"icon={<ExportOutlined />}permission="product:export"onClick={handleExport}>導出數據</AuthButton>,selectedRowKeys.length > 0 && (<AuthButton key="batchDelete"dangerpermission="product:batch-delete"onClick={handleBatchDelete}noPermissionText="無批量刪除權限"disableWhenNoPermission>批量刪除</AuthButton>)]}request={async (params) => {// 模擬從后端獲取產品數據return {data: mockProducts,success: true,total: mockProducts.length,};}}/>{/* 新建/編輯模態框 */}<Modaltitle={editRecord ? "編輯產品" : "新建產品"}open={createModalVisible || !!editRecord}onCancel={() => {setCreateModalVisible(false);setEditRecord(null);}}destroyOnClosefooter={null}><ProductForm initialValues={editRecord}onSuccess={() => {setCreateModalVisible(false);setEditRecord(null);// 刷新表格}}/></Modal></PageContainer>);
};// 模擬產品數據
const mockProducts: Product[] = [{ id: '1', name: 'MacBook Pro', category: 'Laptop', price: 14999, stock: 15, owner: 'user-001' },{ id: '2', name: 'iPhone 14', category: 'Phone', price: 7999, stock: 30, owner: 'user-002' },{ id: '3', name: 'iPad Air', category: 'Tablet', price: 4799, stock: 8, owner: 'user-003' },
];export default ProductList;

后端接口設計 (Node.js + Express 示例)

權限接口控制器 (permissionController.js)

// 后端控制層 - permissionController.js
const db = require('../models');
const { Op } = require('sequelize');class PermissionController {// 獲取用戶所有權限async getPermissions(req, res) {try {const userId = req.user.id; // 從認證信息中獲取用戶ID// 1. 獲取用戶角色const user = await db.User.findByPk(userId, {include: [{model: db.Role,as: 'roles',attributes: ['id', 'name'],through: { attributes: [] }}]});// 2. 獲取角色關聯的權限const roleIds = user.roles.map(role => role.id);const rolesWithPermissions = await db.Role.findAll({where: { id: { [Op.in]: roleIds } },include: [{model: db.Permission,as: 'permissions',attributes: ['code'],through: { attributes: [] }}]});// 3. 獲取用戶自定義權限const customPermissions = await db.UserPermission.findAll({where: { userId },include: [{model: db.Permission,attributes: ['code']}]});// 4. 合并權限并去重const permissions = new Set();// 角色權限rolesWithPermissions.forEach(role => {role.permissions.forEach(permission => {permissions.add(permission.code);});});// 自定義權限customPermissions.forEach(up => {permissions.add(up.Permission.code);});res.json({success: true,permissions: Array.from(permissions)});} catch (error) {console.error('獲取權限失敗', error);res.status(500).json({success: false,message: '獲取權限失敗'});}}// 檢查特定權限async checkPermission(req, res) {try {const userId = req.user.id;const { permission, context } = req.body;// 1. 獲取用戶所有權限const { permissions } = await this._getAllPermissions(userId);// 2. 檢查是否存在完全匹配的權限if (permissions.has(permission) {return res.json({ hasPermission: true });}// 3. 數據權限檢查(示例:檢查產品所有權)if (permission.startsWith('product:') && context?.productId) {const product = await db.Product.findByPk(context.productId);if (product && product.ownerId === userId) {// 檢查擁有者的特殊權限(如 product:update:own)const ownPermission = `${permission}:own`;if (permissions.has(ownPermission)) {return res.json({ hasPermission: true });}}}res.json({ hasPermission: false });} catch (error) {console.error('權限檢查失敗', error);res.status(500).json({success: false,message: '權限檢查失敗'});}}// 內部方法:獲取用戶所有權限async _getAllPermissions(userId) {// ...與getPermissions方法類似// 返回Set對象包含所有權限}
}module.exports = new PermissionController();

權限接口路由 (permissionRoutes.js)

const express = require('express');
const router = express.Router();
const permissionController = require('../controllers/permissionController');
const authMiddleware = require('../middlewares/auth');// 權限認證中間件
router.use(authMiddleware);// 獲取用戶權限列表
router.get('/permissions', permissionController.getPermissions);// 檢查特定權限
router.post('/permissions/check', permissionController.checkPermission);module.exports = router;

數據庫模型 (models.js)

module.exports = (sequelize, DataTypes) => {const User = sequelize.define('User', {id: { type: DataTypes.UUID, primaryKey: true },username: DataTypes.STRING,email: DataTypes.STRING,// ...});const Role = sequelize.define('Role', {id: { type: DataTypes.UUID, primaryKey: true },name: DataTypes.STRING,description: DataTypes.STRING});const Permission = sequelize.define('Permission', {id: { type: DataTypes.UUID, primaryKey: true },code: { type: DataTypes.STRING, unique: true },name: DataTypes.STRING,category: DataTypes.STRING,description: DataTypes.TEXT});// 關聯關系User.belongsToMany(Role, { through: 'UserRole' });Role.belongsToMany(User, { through: 'UserRole' });Role.belongsToMany(Permission, { through: 'RolePermission' });Permission.belongsToMany(Role, { through: 'RolePermission' });User.belongsToMany(Permission, { through: 'UserPermission' });Permission.belongsToMany(User, { through: 'UserPermission' });return { User, Role, Permission };
};

權限系統特點

1. 權限等級體系

2. 權限分類

權限類型描述示例
?功能權限?控制操作入口product:create
?數據權限?控制數據訪問范圍product:read:department
?操作權限?控制特定操作order:approve
?狀態權限?控制狀態變更order:status:change

3. 緩存策略

// 前端權限緩存機制
const PERMISSION_CACHE_KEY = 'user_permissions';
const CACHE_TTL = 30 * 60 * 1000; // 30分鐘async function getPermissionsWithCache() {const cached = localStorage.getItem(PERMISSION_CACHE_KEY);if (cached) {const { permissions, timestamp } = JSON.parse(cached);if (Date.now() - timestamp < CACHE_TTL) {return permissions;}}// 后端獲取新權限const res = await getPermissions();const dataToCache = {permissions: res.permissions,timestamp: Date.now()};localStorage.setItem(PERMISSION_CACHE_KEY, JSON.stringify(dataToCache));return res.permissions;
}// 強制刷新
function refreshPermissions() {localStorage.removeItem(PERMISSION_CACHE_KEY);getPermissionsWithCache();
}

權限管理最佳實踐

1. 權限按鈕使用示例

// 基本按鈕
<AuthButton permission="product:create">創建產品</AuthButton>// 帶圖標和提示的按鈕
<AuthButton permission="product:delete"icon={<DeleteOutlined />}noPermissionText="無刪除權限"
>刪除
</AuthButton>// 數據上下文權限
<AuthButton permission="product:update"context={{ owner: currentUser.id }}
>編輯我的產品
</AuthButton>

2. 權限控制策略

// 頁面級別權限控制
const AdminPage = () => {return (<Access permission="system:admin"><div>管理員面板</div>{/* 管理員功能區域 */}</Access>);
};// 條件渲染多個元素
<div><Access permission="reports:view"><DashboardSection /><ReportChart /></Access><Access permission="user:manage"><UserManagementSection /></Access>
</div>

3. 高級權限場景

?動態權限加載?

const [dynamicPermissions, setDynamicPermissions] = useState(false);useEffect(() => {checkPermission('advanced:feature').then(res => {setDynamicPermissions(res.hasPermission);});
}, []);return (<div>{dynamicPermissions && (<Button>高級功能</Button>)}</div>
);

?批量權限檢查?

const [buttonsEnabled, setButtonsEnabled] = useState({});useEffect(() => {const permissionsToCheck = ['order:create','order:approve','order:export'];checkMultiplePermissions(permissionsToCheck).then(res => {setButtonsEnabled(res);});
}, []);// ...
<Button disabled={!buttonsEnabled['order:create']}>新建訂單</Button>

權限審計與監控

// 前端操作日志記錄
function logPermissionAction(action: string, hasPermission: boolean, context?: any
) {analytics.track('permission_action', {action,hasPermission,userId: currentUser.id,context,timestamp: new Date().toISOString(),pathname: window.location.pathname});
}// 在權限鉤子中調用
const hasPermission = (perm: string, context?: any) => {const hasPerm = /* 權限檢查邏輯 */;if (perm.startsWith('sensitive:')) {logPermissionAction(perm, hasPerm, context);}return hasPerm;
};

這個基于 Ant Design Pro 和 TypeScript 的權限按鈕系統提供了完整的解決方案:

  1. ?前后端分離?:前端基于組件化設計,后端提供靈活的權限服務
  2. ?細粒度控制?:支持功能權限和數據權限
  3. ?靈活配置?:多種權限控制策略(隱藏、禁用、提示)
  4. ?權限緩存?:優化性能減少后端請求
  5. ?權限繼承?:支持角色權限和直接用戶權限
  6. ?上下文權限?:支持基于數據所有權的權限檢查
  7. ?完整審計?:記錄敏感操作便于追蹤

在企業級項目中,這樣的權限系統可以輕松集成到現有架構中,提供安全可靠的權限控制能力。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85559.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85559.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85559.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

RAG工程落地:處理文檔中表格數據

在 RAG&#xff08;Retrieval-Augmented Generation&#xff09;工程落地過程中&#xff0c;處理文檔中的表格數據 是一個非常重要但復雜的問題&#xff0c;特別是針對技術文檔、報告、論文等結構化強的資料。比如PDF文檔里的表格數據&#xff0c;如下&#xff1a; RAG處理表格…

大模型在肺癌預測及個性化診療方案中的應用研究

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的與創新點 1.3 國內外研究現狀 二、大模型預測肺癌的原理與方法 2.1 大模型概述 2.2 數據收集與預處理 2.3 特征工程 2.4 模型訓練與優化 三、術前預測與方案制定 3.1 病情評估 3.1.1 腫瘤大小、位置及分期預測 3.1.…

如何高效分享WordPress博客文章

在當今信息過載的時代&#xff0c;寫好一篇優秀的 WordPress 博客文章只是起點&#xff0c;如何有效地分享給更多讀者才是成功的關鍵所在。對于新手用戶而言&#xff0c;選擇合適的工具和平臺尤為重要。現在許多服務器提供商支持一鍵安裝WordPress功能&#xff0c;比如 Hosteas…

以孝治家有機農業生態文明考察組赴邯鄲心田農場考察學習

按照2025年中共中央、國務院印發了關于《鄉村全面振興規劃&#xff08;2024—2027年&#xff09;》的戰略部署。根據《鄉村全面振興規劃》提出的“堅持人與自然和諧共生。牢固樹立和踐行綠水青山就是金山銀山的理念&#xff0c;落實節約優先、保護優先、自然恢復為主的方針&…

解決el-input無法輸入的問題 vue2+element el-input

問題描述: 在el-dialog中el-form組件來做表單提交 中文輸入模式: 在初次輸入的時候能輸入內容 但是再次輸入無法更改內容 英文輸入模式: 只能輸入一個英文 很多文章都是說 是雙向綁定的問題 但是我仔細看了 變量的雙向綁定確實沒毛病 直到我發現了是因為我el-input中的圖…

16_集成學習

描述 集成學習&#xff08;Ensemble Learning&#xff09;是一種通過結合多個模型的預測結果來提高整體性能的技術。集成學習的核心思想是通過多個弱學習器的組合&#xff0c;可以構建一個強學習器。 sklearn中常見的集成學習算法&#xff1a; Bagging&#xff1a;通過自助采…

學習STC51單片機43(芯片為STC89C52RCRC)智能小車9(語音識別小車)

每日一言 不必與他人比較速度&#xff0c;你走的每一步都在書寫自己的傳奇。 案例&#xff1a;語音識別小車 這個是最后一個功能了&#xff0c;其實就是用語音功能讓小車自己切換各種模式&#xff0c;當然了我們需要先學習一下語音模塊 硬件&#xff1a;SU-03T 這個叫做非特定…

Android 中 解析 XML 字符串的幾種方式

在 Android 開發中&#xff0c;解析 XML 文件有多種方式&#xff0c;每種方式都有其特點和適用場景。常見的 XML 解析方式有 DOM 解析、SAX 解析 和 XmlPullParser 解析。 1、DOM 解析 DOM&#xff08;Document Object Model&#xff09;解析是一種基于樹結構的解析方式&#…

云端算力革命:川翔云電腦如何重新定義創作自由

在設計與科技深度融合的時代&#xff0c;高性能硬件的桎梏正成為創意釋放的最大障礙。川翔云電腦以云端算力為支點&#xff0c;通過彈性算力、高效存儲、多端接入三大核心優勢&#xff0c;讓頂級 GPU 資源觸手可及。 一、核心優勢&#xff1a;突破物理極限的云端工作站 彈性算…

1.容器技術與docker環境部署

一&#xff1a;docker概述 因為 Docker 輕便、快速的特性&#xff0c;可以使應用達到快速迭代的目的。每次小的變更&#xff0c;馬上就可以看到效果&#xff0c;而不用將若干個小變更積攢到一定程度再變更。每次變更一小部分其實是一種非常安全的方式&#xff0c;在開發環境中…

關于 RSA:RSA 加密算法過程

RSA 是一種非對稱加密算法&#xff0c;用“公鑰”加密&#xff0c;用“私鑰”解密&#xff0c;保證數據傳輸安全。 比喻理解&#xff1a;鎖和鑰匙 想象一下&#xff1a; 公鑰是“上鎖的鎖”&#xff0c;別人可以用它鎖住箱子&#xff08;加密&#xff09;&#xff0c;但打不開…

SM3算法C語言實現(無第三方庫,帶測試)

一、SM3算法介紹 SM3算法是中國國家密碼管理局&#xff08;OSCCA&#xff09;于2010年發布的商用密碼散列函數標準&#xff0c;屬于我國自主設計的密碼算法體系之一 &#xff0c;標準文檔下載地址為&#xff1a;SM3密碼雜湊算法 。SM3算法輸出長度為256位&#xff08;32字節&a…

搜索二叉數(c++)

前言 在學習數據結構的時候我們學習過二叉樹&#xff0c;那啥是搜索二叉樹呢&#xff1f;我們知道單純的二叉樹沒有增刪查改的實際意義&#xff0c;因為沒有任何限制條件的二叉樹其實用處很局限。但是堆就不一樣了&#xff0c;他就是一個二叉樹加上了大小堆的限制條件&#xf…

vc MFC在opencv的Mat圖像上顯示中文:Mat轉位MFC的CImage,畫圖寫文字,再轉回Mat

vc MFC在opencv的Mat圖像上顯示中文&#xff1a;Mat轉位MFC的CImage&#xff0c;畫圖寫文字&#xff0c;再轉回Mat // Step 1 創建CImage獲取dc int iImgW matImgSized.cols; int iImgH matImgSized.rows; int iChannel matImgSized.channels(); bool bCon matImgSized.is…

Docker環境部署

目錄 一&#xff1a;Docker 概述 1.什么是 Docker 2:Docker 的優勢 3.Docker 的應用場景 4:Docker 核心概念 二:Docker 安裝 1:本安裝方式使用阿里的軟件倉庫 三:Docker 鏡像操作 1:獲取鏡像 2.查看鏡像信息 3.查看鏡像詳細信息 4.修改鏡像標簽(老名字新名字) 5:刪…

Axios 攔截器實現原理深度剖析:構建優雅的請求處理管道

在構建現代前端應用時&#xff0c;網絡請求處理是關鍵環節。作為最流行的HTTP客戶端庫之一&#xff0c;Axios通過其攔截器機制&#xff08;Interceptors&#xff09;提供了強大的請求/響應處理能力。本文將深入Axios源碼&#xff0c;揭示攔截器背后的精妙設計與實現原理。 一、…

寶塔安裝nginx-http-flv-module,音視頻直播,第二篇

1&#xff0c;先安裝環境安裝nginx 先卸載原有nigix nigix 大于等于 1.2.6 cd /www/server # 進入寶塔目錄 yum install git -y git clone https://gitee.com/winshining/nginx-http-flv-module.git 使用源碼安裝nigix 在 自定義模塊 區域點擊「添加」&#xff0c;填寫以下參…

低延遲4G專網:保障關鍵業務的實時通信

在工業互聯網、智慧園區、應急通信等對“實時性”要求極高的場景中&#xff0c;網絡延遲的高低&#xff0c;直接決定了業務運行的可靠性與安全性。IPLOOK依托多年核心網研發經驗&#xff0c;推出的低延遲4G專網解決方案&#xff0c;正是為此類關鍵業務打造的“通信專線”&#…

NLP語言發展路徑分享

自然語言處理初期發展歷程 早期&#xff1a;離散表示 one-hot&#xff08;只表達“有/無”&#xff0c;語義完全丟失&#xff09;→ n-gram&#xff08;局部上下文&#xff0c;但高維稀疏&#xff09;→ TF-IDF&#xff08;考慮詞頻與權重&#xff0c;但不能表達詞關聯&#x…

如何將文件從安卓設備傳輸到電腦?

將文件從 Android 手機傳輸到 PC 是例行公事嗎&#xff1f;想讓文件傳輸更輕松嗎&#xff1f;幸運的是&#xff0c;您可以從本文中獲得 7 種方法&#xff0c;其中包含詳細的步驟&#xff0c;幫助您輕松了解如何將文件從 Android 傳輸到 PC&#xff0c;涵蓋了從無線工具到傳統 U…