这个标题包含了系统篇3个字,是什么意思呢?我在这里做出解释,系统篇意指包含前台和后台的关联性功能实现,同时重心放在逻辑解释上。
可能会有人好奇,既然有系统篇,那么相对系统篇还有哪些篇呢?这里我就按照商业价值从小到大罗列几个不同种类的篇章,帮助读者选择性地阅读我的文章。(如果要发布成一本书,这里就相当于序言。)就像修炼一样,划分不同的境界,每个境界有特定的标识。分别还有入门篇、进阶篇、接口篇。也就是按照商业价值从小到大分别是入门篇、进阶篇、接口篇、系统篇。
现在解释下每个篇的含义。系统篇在第一段已经解释过了,后面就不重复解释了。入门篇意指某种技术框架学习前所涉及的一些安装、配置、工具等支撑性东西的介绍。进阶篇意向某种技术框架的技术点的语法用法解释。接口篇意指前端后端数据交换的展示、解释等,目前接口篇我没在标题中标明,只是在标题中涵盖了前端和后端的框架名。
回归本篇文章正题,展示图片。这里的展示图片指的是前台展示图片,为了配合前台展示图片,还实现了后台上传图片和删除图片,下面先展示下后台效果图和前台效果图,帮助读者建立一个感性的认识。
这是后台效果图
这是前台H5端的效果图
这是前台抖音小程序端的效果图
后台效果图中那个V字logo图不用看,这是建立工程时自带的展示,我没有删掉。
后台效果图是一个PC端的浏览器的展示界面,重点看圈出来的写着Click to upload的按钮,这个按钮就是负责上传图片功能的,点击这个按钮会弹出选择图片的窗口。图中还展示了3张上传好的图片,没上传之前是空白的,其中鼠标移到第2项上传好的图片提示项中就会显示出这一项圈出来的删除交叉符。点击这个删除交叉符这里还实现了删除图片功能。
前台效果图有2张,以抖音小程序效果图为例介绍就好。
抖音小程序效果图展示了两部分内容,一部分是导航,导航是通过一个底部tabbar展示,点击组件项后所看到的页面,这个不是首页。然后在组件页内置一个tabbar,然后点击Posts页再看到如今的效果图。第二部分就是Posts页下图片列表的展示。
这里再介绍下所用到的技术框架,前台前端使用uniapp框架,后台前端使用Vue3+ElementPlus框架,后端统一使用thinkphp8框架。
要在前台展示,那么就要先在后台实现对应接口功能
实现接口功能,需要在mysql数据库先建立一张图片表来支撑
CREATE TABLE `am8_head2` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8
name这个字段用来存放图片路径就好。
后端采用自动多应用模式建立项目,建立admin应用,分别建立Index控制器和Head2模型。
在Index控制器中实现上传图片接口如下
public function upload()
{
$head = new Head2;
$file = request()->file('image');
$savename = \think\facade\Filesystem::disk('public')
->putFile( 'topic', $file);
$head->name = $savename;
$head->save();
$head_id = $head->getKey();
return mySuccessResponse([
'id' => $head_id,
'name' => $savename,
'url' => 'http://auto.multi.tp8.com/storage/'.$savename
]);
}
该接口分3步实现,第一步获取图片并保存图片,代码如下
$file = request()->file('image');
$savename = \think\facade\Filesystem::disk('public')
->putFile( 'topic', $file);
第二步将文件路径写入head2数据表并获取自动生成的图片id
$head->name = $savename;
$head->save();
$head_id = $head->getKey();
第三步响应到前端的数据
return mySuccessResponse([
'id' => $head_id,
'name' => $savename,
'url' => 'http://auto.multi.tp8.com/storage/'.$savename
]);
mySuccessResponse方法是个封装好的json格式数据返回方法,大家自己封装适合自己的,这里不展示了。返回的数据有3项,分别是id、name、url,都是前端所需要的。
到目前为止,可能代码对于某些人来说是很粗糙的,但是我这个小系统就是个粗糙的迭代系统,不要去介意代码粗糙。
这里url项前半部分用了http://auto.multi.tp8.com,这是我项目的公用域名,这些个图片不独属于某个应用的,故而用公用域名。每个应用有各自的绑定域名。
在Index控制器中实现删除文件接口如下
public function deleteFile()
{
$head = Head2::find(request()->param('id'));
$name = $head->name;
$head->delete();
$search = '\\';
$replacedString = str_replace($search, DIRECTORY_SEPARATOR, $name);
$search = '/';
$replacedString2 = str_replace($search, DIRECTORY_SEPARATOR, $replacedString);
unlink(app()->getRootPath().'public'.DIRECTORY_SEPARATOR.'storage'
.DIRECTORY_SEPARATOR.$replacedString2);
return mySuccessResponse();
}
该接口分3步实现。第一步删除数据库中图片记录同时临时保存取出来的图片路径
$head = Head2::find(request()->param('id'));
$name = $head->name;
$head->delete();
第二步删除图片,由于在windows和linux系统中文件路径所用的分隔符不一样,因此做了适配处理
$search = '\\';
$replacedString = str_replace($search, DIRECTORY_SEPARATOR, $name);
$search = '/';
$replacedString2 = str_replace($search, DIRECTORY_SEPARATOR, $replacedString);
unlink(app()->getRootPath().'public'.DIRECTORY_SEPARATOR.'storage'
.DIRECTORY_SEPARATOR.$replacedString2);
第三步就是告诉前端删除成功了
return mySuccessResponse();
模型的代码没有,只是建立模型,继承think\Model就好。
是时候介绍下后台界面代码了
先摆放界面元素
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action="http://admin.am8.com/index/upload"
name="image"
list-type="picture"
:on-success="handleSuccess"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:limit="3"
:on-exceed="handleExceed"
>
<el-button type="primary">Click to upload</el-button>
<template #tip>
<div class="el-upload__tip">
jpg/png files with a size less than 500KB.
</div>
</template>
</el-upload>
</template>
第一个元素是vue那个V型logo,区分下就好,不用关注
<img alt="Vue logo" src="./assets/logo.png" />
第二个元素就是上传组件
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action="http://admin.am8.com/index/upload"
name="image"
list-type="picture"
:on-success="handleSuccess"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:limit="3"
:on-exceed="handleExceed"
>
,,,
</el-upload>
下面介绍下这段代码的含义
表单按钮的数据绑定是fileList对象
v-model:file-list="fileList"
后端上传的接口设置
action="http://admin.am8.com/index/upload"
上传图片字段的命名,对应后端上传文件的获取名
name="image"
上传图片的展示类型,picture表示展示用fileList的name和url属性展示,前面后端接口upload返回的name和url就是用来更新这里的name和url的。如果使用text,则仅仅用name属性展示
list-type="picture"
上传成功的方法设置
:on-success="handleSuccess"
点击预览的方法设置,这里我没有实现这个功能,只是简单打印了一下
:on-preview="handlePreview"
删除图片的方法设置
:on-remove="handleRemove"
点击交叉图标后真实删除图片之前的方法设置
:before-remove="beforeRemove"
限制每次可以上传的图片数
:limit="3"
超过限制图片数的警告方法设置
:on-exceed="handleExceed"
上传组件内部包含了按钮和提示文字
<el-button type="primary">Click to upload</el-button>
<template #tip>
<div class="el-upload__tip">
jpg/png files with a size less than 500KB.
</div>
</template>
摆放完界面元素后就要实现操作元素了,代码如下
<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { UploadProps, UploadUserFile } from 'element-plus'
import axios from 'axios';
const fileList = ref<UploadUserFile[]>([
])
const handleSuccess: UploadProps['onSuccess'] = (response, uploadFile, uploadFiles) => {
fileList.value.pop()
fileList.value.push(response.data)
}
const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
axios.post('http://admin.am8.com/index/deleteFile', {
id: file.id,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
console.log(uploadFile)
}
const handleExceed: UploadProps['onExceed'] = (files, uploadFiles) => {
ElMessage.warning(
`The limit is 3, you selected ${files.length} files this time, add up to ${
files.length + uploadFiles.length
} totally`
)
}
const beforeRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => {
return ElMessageBox.confirm(
`Cancel the transfer of ${uploadFile.name} ?`
).then(
() => true,
() => false
)
}
</script>
上传成功之后就用后端的数据替换掉前端的默认文件名
const handleSuccess: UploadProps['onSuccess'] = (response, uploadFile, uploadFiles) => {
fileList.value.pop()
fileList.value.push(response.data)
}
执行真实删除图片确认后,向后端发送删除接口
const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
axios.post('http://admin.am8.com/index/deleteFile', {
id: file.id,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
其他方法自己去看,就不展开介绍了。接下来介绍前台后端接口
首先建立mp_toutiao和h5应用,这2个应用所需的控制器和模型是一样的。那么为什么还要分开来写呢?因为每个渠道都有各自的差异,正是因为这些差异才导致各自的商业价值。而所谓跨端技术只是一种适应多端的技术,在有需求的时候使用,不是让大家去抹杀差异的。如果仅仅是为了复用方便而降低要求去使用跨端技术,那其实是本末倒置。这里既然2个应用代码大致一样,我就用mp_toutiao应用为例去介绍好了。
建立Index控制器和head2模型。
在Index控制器中编写获取图片列表接口
public function getHead2List()
{
$list = Head2::limit(3)->order('id desc')->select();
$list2 = [];
foreach ($list as $key => $value) {
$search = '\\';
$replace = '/';
$replacedString = str_replace($search, $replace, $value->name);
$list2[$key] = 'http://auto.multi.tp8.com/storage/'.$replacedString;
}
return mySuccessResponse($list2);
}
从head2数据表拿出图片数据,然后处理成前端image组件需要的格式,使用公用域名,这里尽可能处理好逻辑,前端复制显示就好。前端的重心是显示和元素操作,尽量不要带给前端太多的逻辑处理。
模型的代码没有,只是建立模型,继承think\Model就好。
前台前端代码编写如下
<template>
<div class="tab">
<image
v-for="item in list"
class="logo"
:src="item"
></image>
</div>
</template>
<script>
export default {
data() {
return {
list:[]
}
},
mounted() {
uni.request({
url:getApp().globalData.server_url+"index/getHead2List",
method:"GET",
success:(result) => {
console.log(result);
if (result.data.code === getApp().globalData.my_success_code) {
this.list = result.data.data;
} else {
if (result.data.code === getApp().globalData.my_fail_code) {
uni.showModal({
title: '错误',
content: result.data.msg, // 显示后端返回的错误信息
showCancel: false // 不显示取消按钮
});
}
}
}
,
fail:function(error){
console.log(error);
}
})
},
}
</script>
<style>
.logo {
height: 200rpx;
width: 200rpx;
}
</style>
解释下上面的代码,摆放元素代码如下
<template>
<div class="tab">
<image
v-for="item in list"
class="logo"
:src="item"
></image>
</div>
</template>
item就是后端返回的图片完整路径,list就是用来接收后端返回的图片列表
初始化请求后端数据,代码如下
uni.request({
url:getApp().globalData.server_url+"index/getHead2List",
method:"GET",
success:(result) => {
console.log(result);
if (result.data.code === getApp().globalData.my_success_code) {
this.list = result.data.data;
} else {
if (result.data.code === getApp().globalData.my_fail_code) {
uni.showModal({
title: '错误',
content: result.data.msg, // 显示后端返回的错误信息
showCancel: false // 不显示取消按钮
});
}
}
}
,
fail:function(error){
console.log(error);
}
})
其中getApp().globalData.server_url,getApp().globalData.my_success_code,getApp().globalData.my_fail_code都是预先写好的全局变量,各自写好自己的就好。server_url是mp_toutiao应用绑定的域名,my_success_code是后端响应的成功标识,my_fail_code是后端响应的失败标识。