Vue通过file控件上传文件到Node服务器
功能: 1.多文件同时上传、2.拖动上传、3.实时上传进度条、4.中断上传和删除文件、5.原生file控件的美化
搁置的功能: 上传文件夹、大文件切片上传、以及其他限制条件未处理
Node服务器的前置准备:
新建文件夹: file_upload_serve初始化npm: npm init -y安装工具: npm add express multernodemon工具: npm install nodemon -gaxios: npm install axios -sNode运行版本: 18.17.1修改package.json文件"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},
改为:监听app.js"scripts": {"dev": "nodemon ./app.js"},
启动: npm run dev
Node > file_upload_serve > app.js
按前置准备完成,其他无需更改,请求部分全在app.js
/** @Description: * @Last Date: Do not edit*/
const express = require('express')
// post请求解析body
const bodyParser = require('body-parser')
// 上传工具库
const multer = require('multer')
const { writeFileSync } = require('fs')
const { resolve } = require('path')
const path = require('path')
const fs = require('fs')const app = express()
app.use(bodyParser.json({limit: '10mb', extended: true}))
// 静态资源共享(下载需要)
app.use(express.static(path.join(__dirname, 'public')))
// const storage = multer.diskStorage({
// destination: function (req, file, callback) {
// // 第一个参数: errorMessage; 参数2: 目标,即下载到哪个文件夹下
// callback(null, 'uploads/')
// },
// filename: function (req, file, callback) {
// // 获取上传文件的后缀名
// const ext = file.originalname.split('.')[1]
// callback(null, Date.now() + '.' + ext)
// }
// })
const storage = multer.diskStorage({destination: (req, file, cb) => {cb(null, 'uploads/') // 分片存储目录},filename: (req, file, cb) => {const ext = file.originalname.split('.')[1]if(req.body.rename) {cb(null, Date.now() + '.' + ext) // 单文件名} else {cb(null, `${req.body.index}-${req.body.fileName}`) // 分片文件名}}
})// 生成upload对象
const upload = multer({storage,
})// 设置请求头
app.all('*', (req, res, next) => {// 允许所有不同源的地址访问res.header('Access-Control-Allow-Origin', '*');// 跨域允许的请求方式res.header('Access-Control-Allow-Methods', 'GET, POST');// x-ext: 获取文件的后缀名// res.header('Access-Control-Allow-Headers', 'Content-Type, x-ext');// res.header('Access-Control-Allow-Headers', 'Content-Type');res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, x-ext");if (req.method.toLowerCase() == 'options'){res.send(200); //让options尝试请求快速结束} else {next()}})/* 上传方式1: multipart/form-data** upload.single 单文件上传*/
app.post('/file', upload.single('file'), (req, res) => {if(req.file){res.send('formData上传成功')} else {res.send('form-data上传失败')}
})/* 上传方式2: base64** upload.single 单文件上传*/
app.post('/base64', (req, res) => {const { file, ext, fileName } = req.bodyconst binaryData = Buffer.from(file, 'base64')if(!fileName) {writeFileSync(resolve(__dirname, 'uploads/' + Date.now() + '.' + ext), binaryData, 'binary')} else {writeFileSync(resolve(__dirname, 'uploads/' + fileName), binaryData, 'binary')}res.send('base64文件流上传成功')
})/* 上传方式3: binary 二进制** upload.single 单文件上传*/
app.post('/binary', (req, res) => {const ext = req.headers['x-ext']const buffers = []req.on('data', chunk => {buffers.push(chunk)}).on('end', () => {const binaryData = Buffer.concat(buffers)writeFileSync(resolve(__dirname, 'uploads/' + Date.now() + '.' + ext), binaryData, 'binary')res.send('二进制流上传成功')})
})
/* 多文件上传: formData** upload.array('formData中的字段名', 最大上传数量): */
app.post('/files', upload.array('files', 4), (req, res) => {console.log(req.files)if(req.files){res.send('多文件formData上传成功')} else {res.send('多文件formData上传失败')}
})
/* 文件下载* __dirname: 代表当前文件<app.js>所在的文件路径*/
app.get('/download', (req, res) => {try{// 下载路径: __dirname 拼接 第二个参数的路径const filePath = path.join(__dirname, '/public/download/1731726859151.txt')res.download(filePath)}catch(e){console.log(e)}})app.post('/merge', async (req, res) => {const uploadPath = '/uploads'let files = fs.readdirSync(path.join(process.cwd(), uploadPath)) // 获取所有的分片数据console.log(files)console.log(req.body.fileName)files = files.sort((a, b) => a.split('-')[0] - b.split('-')[0]) // 将分片按照文件名进行排序const writePath = path.join(process.cwd(), uploadPath, `${req.body.fileName}`) // 生成新的文件路径files.forEach((item) => {fs.appendFileSync(writePath, fs.readFileSync(path.join(process.cwd(), uploadPath, item))) // 读取分片信息,追加到新文件路径尾部fs.unlinkSync(path.join(process.cwd(), uploadPath, item)) // 将读取过的分片进行删除})res.send('ok')
})app.listen(8888, () => {console.log("链接成功")})
客户端
<!--* @Description: 功能: 1.多文件同时上传、2.拖动上传、3.实时上传进度条、* 4.中断上传和删除文件、5.原生file控件的美化* 搁置的功能: 上传文件夹、大文件切片上传、以及其他限制条件未处理* @Last Date: Do not edit
-->
<template><div class="container"><header><divclass="box"@drop="handleClick"@dragenter="handleClick"@dragover="handleClick"@dragleave="handleClick"><div class="box-font"><div><span style="display: flex; align-items: center"><i class="el-icon-upload"> </i><p>将目录或多个文件拖拽到此进行扫描</p></span></div><div><span>支持的文件类型: .JPG、.JPEG、.BMP、.PNG、.GIF、.ZIP、</span></div><div><span>每个文件允许的最大尺寸: 1M</span></div></div></div></header><main><div class="main-choose-files-btn"><div class="file-box"><input type="button" class="btn" value="选择文件" /><inputtype="file"class="file"@change="previewMoreFilesByFormData"multiple/></div><div class="file-box"><input type="button" class="btn" value="选择文件夹" /><inputtype="file"class="file"@change="previewMoreFilesByFormData"multiple/></div></div><div><el-table :data="tableData" stripe style="width: 85%"><!-- <el-table-columnv-for="item in tableColumn":key="item.prop":prop="item.prop":label="item.label"></el-table-column> --><el-table-columnprop="name"label="文件名"width="240"fixed></el-table-column><el-table-column prop="type" label="类型"></el-table-column><el-table-column prop="size" label="大小"></el-table-column><el-table-column prop="state" label="状态"><!-- 当template中有多个元素需要切换时,需要在最外层使用div将所有元素包裹住 --><!-- slot-scope="scope" 必须加,否则数据不是响应式的 --><template slot-scope="scope"><div><divv-show="scope.row.progressPercent > 0 &&scope.row.progressPercent < 100"><el-progress:text-inside="true":stroke-width="15":percentage="scope.row.progressPercent"/></div><divv-show="scope.row.progressPercent < 1"slot="reference"class="name-wrapper"><el-tag size="medium"> 待上传 </el-tag></div><divv-show="scope.row.progressPercent === 100"slot="reference"class="name-wrapper"><el-tag size="medium"> 已上传 </el-tag></div></div></template></el-table-column><el-table-column label="操作"><template slot-scope="scope"><i class="el-icon-delete" @click="deleteFile(scope.row)"></i></template></el-table-column></el-table></div></main><footer><el-row><el-button class="foot-btn" size="mini"><span>文件数量: {{ tableData.length }}</span></el-button><el-button class="foot-btn" type="success" plain size="mini">成功数量: {{ successCount }}</el-button><el-button class="foot-btn" size="mini"><span>总大小: {{ countSize }} MB</span></el-button></el-row><el-row class="upload-btn"><el-buttontype="primary":disabled="uploadDisabled"@click="handelUploadMoreFile">开始上传</el-button></el-row></footer></div>
</template><script>
import axios from "axios"
export default {data() {return {ext: undefined, // 文件后缀名tableData: [],tableColumn: [{ prop: "name", label: "文件名" },{ prop: "type", label: "类型" },{ prop: "size", label: "大小" },{ prop: "progressPercent", label: "状态" },{ prop: "option", label: "操作" },],filesNumber: 1, // 列表文件总条数successCount: 0, // 上传成功条数cancelTokens: [], // 存储每个请求的取消方法}},mounted() {// 阻止事件冒泡,防止在拖拽后意外打开新标签页document.body.ondrop = function (event) {event.preventDefault()event.stopPropagation()}},computed: {getByte() {return this.tableData.reduce((a, b) => {return a + b.size}, 0)},// 文件总大小countSize() {return (this.getByte / 1048576).toFixed(2)},// 是否禁用"上传按钮"uploadDisabled() {return this.tableData.length > 0 ? false : true},},methods: {// 读取多个文件previewMoreFilesByFormData(e, drop) {let filesif (!drop) {files = e.target.files} else {files = e}// 获取文件后缀名this.ext = files[0].name.split(".")[1]if (!files) returnvar i = 0var _this = thisvar funcs = function () {if (files[i]) {var reader = new FileReader()reader.onload = function (e) {const uint8Array = new Uint8Array(e.target.result)const str = uint8Array.reduce((prev, byte) => {prev += String.fromCharCode(byte)return prev}, "")let now = new Date()// 由于JS执行速度很快,极大可能会得到一样的时间戳,故将timestamp加上下标// timestamp的作用是在将来删除文件时,作为唯一id对比删除let timestamp = now.getTime()// 将预览的文件中数据转换到table中_this.tableData.push({timestamp: timestamp + i,name: files[i].name,type: files[i].type,size: files[i].size,progressPercent: 0,dataBase64: btoa(str),})// progressPercent 上传进度条i++funcs() // onload为异步调用}reader.readAsArrayBuffer(files[i])}}funcs()},/** 删除上传文件* 不能通过数组下标去删。删除再添加新文件时,下标会重复* @param row(行数据)*/deleteFile(row) {// 删除文件this.tableData = this.tableData.filter((item) => item.timestamp !== row.timestamp)// 中断请求const requestToCancel = this.cancelTokens.find((token) => token.requestId === row.requestId)if (requestToCancel && requestToCancel.cancel) {requestToCancel.cancel("Request was canceled by the user.")}},// 上传文件handelUploadMoreFile() {let _this = thisconst CancelToken = axios.CancelTokenconst List = []for (let i = 0; i < this.tableData.length; i++) {// console.log(source.cancel)const ext = this.ext // 文件后缀名// 为每个请求创建一个新的取消令牌源const cancelTokenSource = CancelToken.source()this.cancelTokens.push({requestId: i,cancel: cancelTokenSource.cancel,})// 给tableData设置"requestId",将来取消请求通过比对id对应到具体的请求this.tableData[i].requestId = ivar a = axios({url: "http://localhost:8888/base64",method: "post",cancelToken: cancelTokenSource.token,data: {ext,fileName: this.tableData[i].name,file: this.tableData[i].dataBase64,},onUploadProgress: (progressEvent) => {/** 上传进度条* progressEvent.loaded: 已上传文件大小* progressEvent.total: 被上传文件的总大小*/_this.tableData[i].progressPercent =(progressEvent.loaded / progressEvent.total) * 100},}).then((res) => {// this.$message({// message: '文件上传成功',// type: 'success'// })// console.log(res)}).catch((error) => {if (axios.isCancel(error)) {console.log(error.message)} else {console.log(error.message)}})List.push(a)}// 合并异步上传Promise.all(List).then((res) => {}).catch((err) => {})},// 处理鼠标拖放事件handleClick(e) {if (e.type == "dragenter") {// this.className = "drag_hover"}if (e.type == "dragleave") {// this.className = ""}if (e.type == "drop") {var files = e.dataTransfer.filesthis.className = ""if (files.length != 0) {this.previewMoreFilesByFormData(files, "drop")}}if (e.type == "dragover") {// e.dataTransfer.dragEffect = "copy"}},},
}
</script><style lang="scss">
body,
html {list-style: none;padding: 0;margin: 0;
}
.container {width: 85%;margin: 25px auto;.box {width: 85%;height: 300px;border-style: dashed; // border虚线border-width: 1px;margin-bottom: 20px;display: flex; /* 启用 Flexbox */justify-content: center; /* 水平居中 */align-items: center; /* 垂直居中 */.box-font {display: flex;flex-direction: column;align-items: center;gap: 30px;span {display: block;}}}main {.main-choose-files-btn {display: flex;gap: 100px;height: 44px;}}footer {margin-top: 20px;.upload-btn {margin-top: 10px;}}
}
// 对原生file控件优化
.btn,
.file {@extend .merge-input;
}
.merge-input {// display: block;position: absolute;width: 75px;height: 35px;color: #fff;border-radius: 4px;border-color: #409eff;
}
.btn {z-index: 2;background: #409eff; // #66b1ff 409effpointer-events: none; /* 让事件传递到下一层,即: btn的层级比file高,但btn能触发file的事件 */
}
.file {z-index: 1;
}
// el-table表头样式修改
.el-table th {font-size: 13px;font-weight: 700;
}.el-table .el-table__header th,
.el-table .el-table__header tr,
.el-table .el-table__header td {background: #f5f8fd;
}.el-icon-upload {font-size: 35px;
}
</style>
相关文章:
Vue通过file控件上传文件到Node服务器
功能: 1.多文件同时上传、2.拖动上传、3.实时上传进度条、4.中断上传和删除文件、5.原生file控件的美化 搁置的功能: 上传文件夹、大文件切片上传、以及其他限制条件未处理 Node服务器的前置准备: 新建文件夹: file_upload_serve初始化npm: npm …...
如何在 SQL Server 中新增账户并指定数据库权限
在日常的数据库维护与开发中,管理用户的权限是必不可少的一环。本文将指导你如何在 SQL Server 中为新用户创建账户,并为其指定相应的数据库权限,使其只能查看有权访问的数据。我们将以步骤和代码示例的方式展示整个流程。用户权限分配包括:读取权限、写入权限、执行权限。…...
c#编码技巧(十九):各种集合特点汇总
.NET 常用集合对比: .NET 常见的线程安全集合 .NET 只读集合...
汽车软件DevOps解决方案
汽车软件DevOps解决方案是专为现代汽车行业设计的一套集成化需求、开发、测试、部署、OTA与监控,旨在加速软件开发流程,提高软件质量和安全性,同时确保整个生命周期的高效性和灵活性。以下是经纬恒润汽车软件DevOps解决方案的关键组成部分和优…...
同步的意义以及机制
一、同步的意义 同步(Synchronization)的意义在于确保在多线程环境中,多个线程对共享资源的访问是安全的,避免竞争条件(race conditions)和数据不一致的情况。 具体来说,同步的核心目标是&…...
leetcode 面试150之 156.LUR 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -…...
启发式搜索算法复现
🏡作者主页:点击! 🤖编程探索专栏:点击! ⏰️创作时间:2024年11月21日19点05分 神秘男子影, 秘而不宣藏。 泣意深不见, 男子自持重, 子夜独自沉。 论文链接 点击开启你的论文编程之旅…...
【IDE】使用指南
定期更新实用技能,建议关注收藏点赞。 友情链接: 点击跳转常见代码编辑器的报错解决方案 目录 常用快捷键pycharm右下角边栏脚本头安装IDE的插件git配置TODO 代码编辑器里有许多小技巧,便于办公。本篇主要以pycharm,vscode等主流常用IDE为…...
设计编程网站集:简述可扩展性系统设计(笔记)
视频连接:简述可扩展性系统设计 三个关键原则 无状态 松散耦合 异步处理 扩展 负载均衡 缓存 分片...
「Mac玩转仓颉内测版25」基础篇5 - 布尔类型详解
本篇将介绍 Cangjie 中的布尔类型,包括布尔值的定义、运算操作符、逻辑运算、布尔类型的常见应用场景及其在条件判断中的应用,帮助开发者理解和使用布尔类型。 关键词 布尔类型定义布尔运算逻辑运算符条件判断常见应用场景 一、布尔类型概述 布尔类型&…...
Fashion-VDM:引领视频虚拟试穿技术的新篇章
引言 随着虚拟现实和增强现实技术的飞速发展,视频虚拟试穿(VVT)已成为时尚产业的一大创新领域。然而,现有的VVT方法在服装细节和时间一致性方面仍存在诸多不足。为了解决这些问题,Johanna Karras等人提出了Fashion-VDM,一种基于视频扩散模型(VDM)的新型视频虚拟试穿技…...
Scala中的集合复习(1)
Map、Set、Array、List 一、集合的三大类 1.序列Seq表示有先后顺序的集合。(Array、List) 2.集Set:表示无序且不重复的集合。 3.映射Map:表示键值对。 Stack:栈,特点是:后进先出。 packag…...
Java依赖包漏洞检测命令
1、漏洞扫描工具 maven插件方式:Dependency-Check 2、命令 检查单个 Maven 工程的安全漏洞 mvn dependency-check:check 这个命令会在 target 目录下生成一个 dependency-check-report.html 文件,其中包含了依赖项的安全漏洞分析报告。 检查多个 M…...
【Java】强制类型转换
int a23; short b(short) a; 小的接受大的接受不了,强制类型转换. 带有Buffer的,带有流的,都是数组。 网络流,文件流都是数组. 这种就是流。 操作系统底层就是C. 没有直系关系的,不让转换 语法不报错,运行…...
RabbitMQ消息可靠性保证机制4--消费端限流
7.7 消费端限流 在类似如秒杀活动中,一开始会有大量并发写请求到达服务端,城机对消息进行削峰处理,如何做? 当消息投递的速度远快于消费的速度时,随着时间积累就会出现“消息积压”。消息中间件本身是具备一定的缓冲…...
查找萤石云IOS Sdk中的编解码接口
2021/1/20 以前的时候,碰到的问题,想把萤石云视频介入到TRTC,但是... 萤石云的IOS接口中没有相应的解码播放库,也就是找不到PlayerSDK对应部分,怎么做呢? 一个是坐等萤石云开放这部分接口,可能…...
erchas
#include <iostream> #include <vector> https://gitee.com/tongchaowei/front-native-page-template/tree/main/image-display/template-01 using namespace std; class BinaryTree { private: vector<char> tree; // 存储二叉树的数组 int size;…...
【网络安全】SSL(一):为什么需要 Keyless SSL?
未经许可,不得转载。 文章目录 背景正文背景 随着网站和应用程序向云端迁移,使用 HTTPS(SSL/TLS)加密流量已成为行业标准。然而,传统的 HTTPS 配置要求服务器持有网站的私钥,这在云计算环境中引发了一系列安全性和合规性问题。一旦云服务器遭到攻击,私钥泄露可能带来不…...
ggplot2 分面图等添加注释文字,相加哪里加哪里: 自定义函数 AddText()
如果分面图上还想再添加文字,只能使用底层的grid包了。 函数定义 # Add text to ggplot2 figures # # param label text you want to put on figure # param x position x, left is 0, right 1 # param y position y, bottom is 0, up 1 # param color text color…...
解读缓存问题的技术旅程
目录 前言1. 问题的突发与初步猜测2. 缓存的“隐身术”3. 缓存策略的深层优化4. 反思与感悟结语 前言 那是一个普通的工作日,团队例行的早会刚刚结束,我正准备继续优化手头的模块时,突然收到了用户反馈。反馈的内容是部分数据显示异常&#…...
洛谷P1597
语句解析 - 洛谷 语句解析 题目背景 木有背景…… 题目描述 一串长度不超过255的 PASCAL 语言代码,只有 a,b,c 三个变量,而且只有赋值语句,赋值只能是一个一位的数字或一个变量,每条赋值语句的格式是 [变量]:[变量或一位整数…...
2411rust,76~79
1.76.0稳定版 此版本较小 ABI兼容更新 函数指针文档中新增的ABI兼容部分介绍了函数签名与ABI兼容的意义.大部分是参数类型和返回类型的兼容,及在当前Rust中兼容的列表.文档仅描述现有兼容的状态. 一个新增功能是,现在保证符和u32是ABI兼容的.它们一直有相同大小和对齐方式,…...
vue2.0前端管理系统界面布局设置
前言 后台管理系统的核心就是用户管理、角色管理(含权限分配)、菜单管理,以及一些业务管理。业务管理通常以及根据不同的角色进行了权限分配。本次任务完成用户管理页面。 一 界面设计 1.引用Element 的Container 布局容器。 以上次博客中…...
4. SQL视图
MySQL中的视图(View)是一种虚拟表,本质是存储了一条SELECT语句。视图并不直接存储数据,而是动态生成结果集,帮助开发者简化查询逻辑和增强数据安全性。本文将从视图的基础概念到实际应用,逐步深入地探讨如何…...
Simulink学习笔记【PID UG联动仿真】
Simulink进行PID控制及调参: 建立系统动力学框图(把状态方程翻译出来),设置成subsystem建立PID反馈回路。示波器叫scope,多变量输出用demux和mux。可以用自动调参Tune模块,调整响应速度和稳定性࿰…...
【Python】30个Python爬虫的实战项目!!!(附源码)
Python爬虫是数据采集自动化的利器。本文精选了30个实用的Python爬虫项目,从基础到进阶,每个项目都配有完整源码和详细讲解。通过这些项目的实战,可以全面掌握网页数据抓取、反爬处理、并发下载等核心技能。 一、环境准备 在开始爬虫项目前…...
uni-app 界面TabBar中间大图标设置的两种方法
一、前言 最近写基于uni-app 写app项目的时候,底部导航栏 中间有一个固定的大图标,并且没有激活状态。这里记录下实现方案。效果如下(党组织这个图标): 方法一:midButton的使用 官方文档:ta…...
什么是Sass,有什么特点
Sass 概述 什么是 Sass? Sass(Syntactically Awesome Style Sheets)是一种 CSS 预处理器,它扩展了 CSS 的功能,使其更加强大和灵活。Sass 允许开发者使用变量、嵌套规则、混合宏、继承等高级特性,从而编写…...
服务器端渲染 (SSR) 与客户端渲染 (CSR)
嘿程序员!我们都知道,新时代的 Javascript 已经彻底改变了现代网站的结构和用户体验。如今,网站的构建更像是一个应用程序,伪装成一个能够发送电子邮件、通知、聊天、购物、支付等的网站。今天的网站是如此先进、互动,…...
数据结构(Java版)第一期:时间复杂度和空间复杂度
目录 一、数据结构的概念 1.1. 什么是数据结构 1.2. 算法与数据结构的关系 二、算法效率 三、时间复杂度 3.1. 大O的渐进表⽰法 3.2. 计算冒泡排序的时间复杂度 3.3. 计算二分查找的时间复杂度 四、空间复杂度 4.1. 空间复杂度 4.2. 冒泡排序的空间复杂度 4.3.…...
高校网站建设需求单/泉州网站建设优化
策略模式【Strategy Pattern】刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开解决棘手问题,嘿,还别说,真是解决了大问题,搞到最后是周瑜陪了夫人又折兵…...
太原做网站培训/文案代写收费标准
前言 : 有很多时候看一些技术文章,总是停留在看的阶段,但是知识的学习在我看来分成三个步骤:first 。 了解-主要动作也就是看,或者听,比较浅显的学习方式,遗忘速度也比较快 ; second…...
php动态网站开发教程/优化师是一份怎样的工作
1.strcpy函数原型:strcpy(str1,str2);功能:将字符串str2复制到字符串str1中,并覆盖str1原始字符串,可以用来为字符串变量赋值返回:str1注意:1)字符串str2会覆盖str1中的全部字符,2)字符串str2的…...
杭州专业网站建设/今日热点新闻排行榜
简介 熊猫眼是一个平时用来学习的练手的项目,做这样一个应用的目的主要有两个: 公司项目因为历史原因还有风险控制方面的问题,新的技术不一定能够应用在现有的版本上。所以手痒了就自己弄个应用写一写,持续的更新增加自己的技能熟…...
为什么要完善网站建设/学seo推广
朋友们,如需转载请标明出处:https://blog.csdn.net/jiangjunshow 声明:在人工智能技术教学期间,不少学生向我提一些python相关的问题,所以为了让同学们掌握更多扩展知识更好的理解人工智能技术,我让助理负…...
网站建设销售好做吗/人民网 疫情
在我们UI设计师在做设计工作的时候,最怕的是什么?那就是不熟悉的场景和业务,那么,今天为大家分享的交互设计师如何做好陌生领域设计?平时工作时公司产品线比较多,可能就会面临做完这个项目接着做下一个项目…...