前言
书接上回,我们实现了批量修改文件的时间,但是却没有实现文件名称的批量修改,是因为我也说过,没有界面的话直接在命令行实现显得有点繁琐,所以我们就通过接口+界面的方式来实现我们这个小需求吧。所以,闲话不多说啦,开始写我们的代码啦~~
本次教程过于啰嗦,所以这里先放上预览地址供大家预览——点我预览,也可到文末直接下载代码先自行体验。。。
简单的说下实现的效果
通常我们在蓝湖上下载的切图是和UI小姐姐定义的图层名相关的,一般下载下来之后我们就需要修改名称,但是一个个修改又显得十分傻逼 ??,所以我们就自己写一下代码自己修改,具体效果如图:
看到这里,是不是也想跃跃欲试啦,所以,我们就开始写我们的代码吧
简单的搭建一下
新建一个 batch-modify-filenames 目录
初始化一个node项目工程
npm init -y
安装依赖,这里依赖比较多,所以下面我会讲一下他们大概是干嘛的
npm i archiver glob koa koa-body koa-router koa-static uuid -S
npm i nodemon -D
koa Nodejs的Web框架
koa-body 解析 post 请求,支持文件上传
koa-router 处理路由(接口)相关
koa-static 处理静态文件
glob 批量处理文件
uuid 生成不重复的文件名
nodemon 监听文件变化,自动重启项目
archiver 压缩成 zip 文件
ps:nodemon 是用于我们调试的,所以他是开发依赖,所以我们需要-D。其他的都是主要依赖,所以-S
配置一下我们的启动命令
{
...
"scripts": {
"dev": "nodemon app.js"
},
...
}
Koa 是什么
既然用到了Koa,那么我们就了解一下他是什么?
Koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架,采用了 async 和 await 的方式执行异步操作。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。也正是因为没有捆绑任何中间件,Koa 保持着一个很小的体积。
通俗点来讲,就是常说的后端框架,处理我们前端发送过去的请求。
上下文(Context)
Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。
Context这里我们主要用到了state、request、response这几个常用的对象,这里我大概讲讲他们的作用。
state 推荐的命名空间,用于通过中间件传递信息和你的前端视图。
req Node 的 Request 对象.
request Koa 的 Request 对象.
res Node 的 Response 对象.
response Koa 的 Response 对象.
ctx.req 和 ctx.request 的区别
通常刚学Koa的时候,估计有不少人弄混这两个的区别,这里就说说他们两有什么区别吧。
最主要的区别是,ctx.request 是 context 经过封装的请求对象,ctx.req 是 context 提供的 node.js 原生 HTTP 请求对象,同理 ctx.response 是 context 经过封装的响应对象,ctx.res 是 context 提供的 node.js 原生 HTTP 响应对象。
所以,通常我们是通过ctx.request获取请求参数,通过ctx.response设置返回值,不要弄混了哦 (⊙o⊙)
ctx.body 和 ctx.request.body 傻傻分不清
以为通常get请求我们可以直接通过ctx.query(ctx.request.query的别名)就可以获得提交过来的数据,post请求的话这是通过body来获取,所以通常我们会通过猜想,以为ctx.body也是ctx.request.body的别名,其实- -这个是不对的。因为我们不仅要接受数据,最重要还要响应数据给前端,所以ctx.body是ctx.response.body的别名。而ctx.request.body为了区分,是没有设置别名的,即只能通过ctx.request.body获取post提交过来的数据。
总结:ctx.body是ctx.response.body的别名,而ctx.request.body是post提交过来的数据
Koa 中间件
Koa 的最大特色,也是最重要的一个设计,就是中间件(middleware)。Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。Koa 中使用 app.use()用来加载中间件,基本上 Koa 所有的功能都是通过中间件实现的。每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next 函数。只要调用 next 函数,就可以把执行权转交给下一个中间件。
下面两张图很清晰的表明了一个请求是如何经过中间件最后生成响应的,这种模式中开发和使用中间件都是非常方便的:
再来看下 Koa 的洋葱模型实例代码:
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(6);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(5);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
app.listen(8000);
怎么样,是不是有一点点感觉了。当程序运行到 await next()的时候就会暂停当前程序,进入下一个中间件,处理完之后才会回过头来继续处理。
理解完这些后就可以开始写我们的代码啦!!! (lll ¬ ω ¬),好像写了好多和这次教程主题没关的东西,见怪莫怪啦
简单的搭建前端项目
既然说到了写界面,这里我们技术栈就采用vue吧,然后UI库的话,大家都用惯了ElementUI,我想大家都特别熟悉了,所以我们这里就采用Ant Design Vue吧,也方便大家对Antd熟悉一下,也没什么坏处
所以,我们就简单的创建一下我们的项目,在我们batch-modify-filenames文件夹下运行vue create batch-front-end,如图所示:
基本上都是无脑下一步,只不过是ant-design-vue用了less,我们为了符合它的写法,我们配置上也采用less。当然,采用sass也是可以的,没什么强制要求。
创建完项目后就是安装依赖了,因为其实我们用到的组件不多,所以这里我们使用按需加载,即需要安装babel-plugin-import,这里babel-plugin-import也是开发依赖,生产环境是不需要的,所以安装的时候需要-D
这里我们用到了一个常用的工具库(类库)—— lodash,我们不一定用到他所有的方法,所以我们也需要安装个babel插件进行按需加载,即babel-plugin-transform-imports,同样也是-D
最后,既然是与后端做交互,我们肯定需要用到一个http库啦,既然官方推荐我们用axios,所以这里我们也要把axios装上,不过axios不是vue的插件,所以不能直接用use方法。所以,这里我为了方便,也把vue-axios装上了。在之后,因为我又不想把最终的zip文件留在服务器上,毕竟会占用空间,所以我以流(Stream)的方式返回给前端,让前端自己下载,那么这里我就采用一个成熟第三方库实现,也就是file-saver,所以最终我们的依赖项就是:
npm install ant-design-vue lodash axios vue-axios file-saver -S
npm install babel-plugin-import babel-plugin-transform-imports -D
配置babel.config.js:
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
[
"import",
{ libraryName: "ant-design-vue", libraryDirectory: "es", style: true },
], // `style: true` 会加载 less 文件,
[
"transform-imports",
{
lodash: {
transform: "lodash/${member}",
preventFullImport: true,
},
},
],
],
};
因为我们这里改成了style: true,按需引入的时候大概会报下面的这个错误:
解决方案这里也说的很清楚了,在https://github.com/ant-design/ant-motion/issues/44这个链接,也有说明Inline JavaScript is not enabled. Is it set in your options?,告诉我们less没开启JavaScript功能,我们需要修改下 lless-loader的配置即可
因为vue-cli4的webpack不像vue-cli2.x,他对外屏蔽了webpack的细节,如果像修改必须创建vue.config.js来修改配置,所以我们创建一个vue.config.js文件,书写下面配置:
module.exports = {
css: {
loaderOptions: {
less: {
javascriptEnabled: true,
},
},
},
};
关掉服务,在重新跑一下npm run serve,看是不是没有报错了?这样子我就可以书写我们的代码了。
编写布局
界面大概长这样子,我想大家写界面应该比我厉害多了,都是直接套用Antd的组件,所以这里我主要分析我们怎么拆分这个页面的组件比较好,怎么定义我们的数据比较好~~
从这个图我们可以看出新文件列表是基于原文件列表+各种设置得出来的,所以新文件列表我们就可以采用计算属性(computed)来实现啦,那么接下来就是拆分我们页面的时候啦。。。
ps:这里我不会详细讲怎么写界面,只会把我觉得对开发有用的讲出来,不然文章就太多冗长了。虽然现在也十分冗长了(;′д `)ゞ
拆分页面
其实从页面的分割线我们大概就可以看出,他是分成 3 个大的子组件了,还有文件列表可以单独划分为孙子组件,所以基本上如图所示:
代码结构如图:
|-- batch-front-end
├─.browserslistrc
├─.eslintrc.js
├─.gitignore
├─babel.config.js
├─package-lock.json
├─package.json
├─README.md
├─vue.config.js
├─src
| ├─App.vue
| ├─main.js
| ├─utils
| | ├─helpers.js
| | ├─index.js
| | └regexp.js
| ├─components
| | ├─ModifyFilename2.vue
| | ├─ModifyFilename
| | | ├─FileList.vue
| | | ├─FileListItem.vue
| | | ├─FileOutput.vue
| | | ├─FileSetting.vue
| | | └index.vue
| ├─assets
| | └logo.png
├─public
| ├─favicon.ico
| └index.html
看到这里,基本知道我是怎么拆分的吧?没错,一共用了四个组件分别是FileSetting(文件名设置)、FileOutput(输出设置)、FileList(输出结果)和FileListItem(列表组件)这么四大块
当然知道怎么拆分了还远远不够的,虽然现在我们只有 4 个组件,所以写起来问题不是那么的大,但是呢。。。写起页面来其实也是比较麻烦的,一般正常的写法是:
文件名设置
输出设置
输出结果
import { Divider } from "ant-design-vue";
import FileList from "./FileList";
import FileOutput from "./FileOutput";
import FileSetting from "./FileSetting";
export default {
name: "ModifyFilename",
components: {
Divider,
FileList,
FileOutput,
FileSetting,
},
computed: {
// 新文件列表
newFiles() {
return this.oldFiles;
},
},
data() {
return {
// 存放这文件名设置的数据
fileSettings: {},
// 存放自定义序号数组
diyForm: {},
// 启用输出设置
enable: false,
// 输出设置后缀名
ext: [],
// 原文件列表
oldFiles: [],
};
},
};
.content {
width: 1366px;
box-sizing: border-box;
padding: 0 15px;
margin: 0 auto;
overflow-x: hidden;
}
思考一下
但是有没有发现,我们写了三个divider组件,要绑定的数据也是相当之多,虽然我都整合在fileSettings了。如果我们要单独拿出来的话,岂不是要累死个人?所以我们思考一下,怎么可以更加方便的书写我们的这个页面。所以我引申出了下面三个问题:
有没有办法可以用一个组件来标识我们导入的另外三个子组件呢?
有没有办法一次性绑定我们要的数据,而不是一个个的绑定呢?
一次性绑定之后,组件间怎么通信呢(因为这里涵盖了子孙组件)?
针对于这三个问题,我分别使用了动态组件、v-bind和provide实现的,接下来我们就讲讲怎么实现它,先上代码:
{ { item.label }}
:is="item.name"
v-bind="{ ...getProps(item.props) }"
@update="
(key, val) => {
update(item.props, key, val);
}
"
/>
import getNewFileList from "@/utils/";
import { Divider } from "ant-design-vue";
import FileList from "./FileList";
import FileOutput from "./FileOutput";
import FileSetting from "./FileSetting";
export default {
name: "ModifyFilename",
components: {
Divider,
FileList,
FileOutput,
FileSetting,
},
// 传递给深层级子组件
provide() {
return {
parent: this,
};
},
data() {
return {
components: [
{
label: "文件名设置",
name: "FileSetting",
props: "fileSettingsProps",
},
{
label: "输出设置",
name: "FileOutput",
props: "fileOutputProps",
},
{
label: "输出结果",
name: "FileList",
props: "fileListProps",
},
],
fileSettingsProps: {
fileSettings: {
filename: {
value: "",
span: 6,
type: "file",
placeholder: "请输入新的文件名",
},
serialNum: {
value: "",
span: 6,
type: "sort-descending",
placeholder: "起始序号(默认支持纯数字或纯字母)",
},
increment: {