技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-1.工具和本地环境
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-2.启动项目
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-3.路由、模型与数据库操作
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-4.跨域且传输数据,并优化后端接口
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-5.用户登录(一),密码的bcrypt(hash)加密与验证
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-6.用户登录(二),token验证
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-7.分类的模型关联和通用CRUD接口
技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-8.使用mavoneditor(vue的markdown编辑器),并批量上传图片
分类内容的接口与管理员接口相同,都是增删改查,如果每个接口都复制一遍再稍作改动,接口页面内容会太多。所以我们对两个接口进行比对,找不同:
只有这里不同,所以我们只要在路由中把这个辨别值改为动态即可:
将这个辨别值当作模型名处理,因为后续数据接口操作实际上就是模型不同,增删改查方法都一样。
接口函数接收模型名(以查找所有数据接口为例):
public function findall(){
// 接收数据
$data = request() -> param();
// return $data;
// 根据模型名判断
if($data['model'] == "admin"){
$db_data = Admin::select();
// 返回查找到的数据
return $db_data;
}else if($data['model'] == "category"){
$db_data = Category::select();
// 返回查找到的数据
return $db_data;
}
}
因为php语言以命名空间为主,所以无法直接使用传值做模型方法使用,故我们需要判断后再用模型方法进行数据操作。
测试原管理员接口:
没问题,仿照管理员功能做出CategorySet.vue和CategoryList.vue:
CategorySet.vue:
<template>
<div>
<h1>{
{id ? '编辑' : '创建'}}分类h1>
<el-form label-width="80px" style="margin-top:20px;" @submit.native.prevent="save">
<el-form-item label="用户名">
<el-input v-model="model.name">el-input>
el-form-item>
<el-form-item>
<el-button type="primary" native-type="submit">保存el-button>
el-form-item>
el-form>
div>
template>
<script>
export default {
props: {
id: {
}
},
data(){
return {
model: {
},
parentOptions: [],
}
},
methods: {
async save(){
let res
if(this.id){
res = await this.$http.put('rest/category/' + this.id, this.model)
}else{
res = await this.$http.post('rest/category', this.model)
}
console.log("en?",res)
this.$router.push('/categories/list')
this.$message({
type: 'success',
message: '保存成功'
})
},
async fetch(){
const res = await this.$http.get('rest/category/' + this.id)
this.model = res.data
},
},
created(){
this.id && this.fetch()
}
}
script>
CategoryList.vue:
<template>
<div>
<h1>分类列表h1>
<el-table :data="items">
<el-table-column prop="id" label="ID" width="220">
el-table-column>
<el-table-column prop="name" label="分类名称">
el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button type="text" size="small" @click="$router.push('/categories/edit/' + scope.row.id)">编辑el-button>
<el-button @click="remove(scope.row)" type="text" size="small">删除el-button>
template>
el-table-column>
el-table>
div>
template>
<script>
export default {
data() {
return {
items: []
}
},
methods: {
async fetch(){
const res = await this.$http.get('rest/category')
this.items = res.data
},
remove(row){
this.$confirm('是否确定要删除分类"' + row.name + '"?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
// 要想使用await,函数必须使用async
// await异步执行,待调用接口获取数据完成后再将值传给res,进行下一步操作
const res = await this.$http.delete('rest/category/' + row.id)
this.$message({
type: 'success',
message: '删除成功!'
});
if(res.status == 200){
// 接口调用成功后,刷新页面
this.fetch()
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
created() {
this.fetch()
}
}
script>
然后制作Category模型:
namespace app\admin\model;
use think\Model;
class Category extends Model{
// 设置字段信息
protected $schema = [
'id' => 'int',
'name' => 'string',
// 父级分类
'parent' => 'string',
];
public function category(){
// 用于后续数据关联使用(分类的无限层级关联)
return $this -> hasMany(Category::class);
}
}
新建数据表:
刷新页面查看:
没有报错,应该成功了,下面我们将全部接口函数改造:
declare (strict_types = 1);
namespace app\admin\controller;
use app\admin\model\Admin;
use app\admin\model\Category;
class Index
{
public function index()
{
return '您好!这是一个[index]示例应用';
}
public function add()
{
// 获取前端传值
$data = request() -> param();
if($data['model'] == "admin"){
// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 使用模型格式化传来的数据
$admin = new Admin;
// 利用模型将数据传到数据库
$admin->save([
'username' => $data['username'],
'password' => $data['password']
]);
// 返回结果
return '新增数据成功';
}else if($data['model'] == "category"){
$admin = new Admin;
// 利用模型将数据传到数据库
$admin->save([
'name' => $data['name'],
]);
// 返回结果
return '新增数据成功';
}
}
public function findall(){
// 接收数据
$data = request() -> param();
// return $data;
// 根据模型名判断
if($data['model'] == "admin"){
$db_data = Admin::select();
// 返回查找到的数据
return $db_data;
}else if($data['model'] == "category"){
$db_data = Category::select();
// 返回查找到的数据
return $db_data;
}
}
public function find()
{
// 获取前端传值
$data = request() -> param();
if($data['model'] == "admin"){
$db_data = Admin::find($data['id']);
// 返回查找到的数据
return $db_data;
}else if($data['model'] == "category"){
$db_data = Category::find($data['id']);
// 返回查找到的数据
return $db_data;
}
}
public function update()
{
// 获取前端传值
$data = request() -> param();
// return $data;
if($data['model'] == "admin"){
// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 静态方法直接更新
$db_data = Admin::update($data, ['id' => $data['id']]);
return '修改数据成功';
}else if($data['model'] == "category"){
// 静态方法直接更新
$db_data = Category::update($data, ['id' => $data['id']]);
return '修改数据成功';
}
}
public function delete()
{
// 获取前端传值
$data= request() -> param();
if($data['model'] == "admin"){
$db_data = Admin::find($data['id']);
$db_data->delete();
return '删除数据成功';
}else if($data['model'] == "category"){
$db_data = Category::find($data['id']);
$db_data->delete();
return '删除数据成功';
}
}
}
新增数据测试:
修改测试:
成功,都没问题,到此通用接口完成。
(1)更改新建分类页面
CategorySet.vue设置上级分类parent,位于分类名之上:
<el-form-item label="上级分类">
<el-select v-model="model.parent">
<el-option v-for="item in parentOptions" :key="item.id" :label="item.name" :value="item.id">el-option>
el-select>
el-form-item>
(2)使用查询接口
下方js使用categories查询分类接口获取分类信息,将获取到的数据传入分类数据parentOptions中:
async fetchParentOptions(){
const res = await this.$http.get('rest/category')
this.parentOptions = res.data
}
此时页面已接收到分类信息,并可以显示分类名了。
因为是直接复制的,所以还是用户名,改一下:
修改添加数据接口,添加parent字段:
此时新建分类:
成功:
这时候修改列表页,将上级分类显示出来:
保存后页面将显示上级分类:
此时我们需要让上级分类以分类名形式显示。
修改查询所有内容接口:
此时刷新页面得到关联分类内容:
但这并不是我们需要的内容,我们要的是上级分类,但是现在给我们查找的是上级分类的所有下级分类,所以再次改动,添加反向查询父级的方法:
改动find_all接口函数:
刷新页面测试:
成功查询到上级分类数据。
修改前端页面:
保存查看页面:
成功。很好。
此时我们再次修改上级分类php1改回php:
由于是id关联,所以上级分类变动后名字一起变化。
此时我们再创建一个vue分类:
居然报错,看来我们之前的代码是将parent当作了必填项?找一下原因:
经过排查,是前端的原因,parent进入页面时被parentOptions: []定义成了数组,所以如果为空的话就是字符串格式。
所以我们需要在数据模型中提前让parent定义成数组格式:
保存再次测试:
没问题,下面正式开始多个上级分类关联,例如加一个分类名为vue.js+tp6全栈开发的题目,上级分类为vue.js和tp6。
只需要修改前端页面,在下拉框中加入一个属性multiple:
又因为我们上一步已经将parent改为数组格式,所以可以放心上传。
测试:
虽然成功了,但是列表页一个都没有显示,查找原因:
然后我试了一下午,都没有成功,摊牌了,我不会,大神教教我!
经过又半天的研究,我转变了思路,部分有些复杂,数据表字段也变了一些,给大家展示下,大家自行研究一段时间研究不出来可以参照一下:
下篇文章学习图片的上传。
Category模型:
namespace app\admin\model;
use think\Model;
class Category extends Model{
// // 定义json数据
protected $json = ['parent_'];
// // 定义json数据查询时返回数组
protected $jsonAssoc = true;
// 设置字段信息
protected $schema = [
'id' => 'int',
'name' => 'string',
'parent0' => 'string',
'parent1' => 'string',
'parent2' => 'string',
'parent3' => 'string',
'parent4' => 'string',
'parent0_' => 'string',
'parent1_' => 'string',
'parent2_' => 'string',
'parent3_' => 'string',
'parent4_' => 'string',
'parent_' => 'string'
];
// 查找下级分类
public function children(){
return $this -> hasMany(Category::class, 'parent', 'id');
}
// 查找上级分类
public function parent0(){
return $this -> belongsTo(Category::class, 'parent0', 'id');
}
public function parent1(){
return $this -> belongsTo(Category::class, 'parent1', 'id');
}
public function parent2(){
return $this -> belongsTo(Category::class, 'parent2', 'id');
}
public function parent3(){
return $this -> belongsTo(Category::class, 'parent3', 'id');
}
public function parent4(){
return $this -> belongsTo(Category::class, 'parent4', 'id');
}
}
接口函数:
declare (strict_types = 1);
namespace app\admin\controller;
use app\admin\model\Admin;
use app\admin\model\Category;
use app\admin\model\News;
class Index
{
public function index()
{
return '您好!这是一个[index]示例应用';
}
public function add()
{
// 获取前端传值
$data = request() -> param();
if($data['model'] == "admin"){
// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 使用模型格式化传来的数据
$db_data = new Admin;
// 利用模型将数据传到数据库
$db_data->save([
'username' => $data['username'],
'password' => $data['password']
]);
// 返回结果
return '新增数据成功';
}else if($data['model'] == "category"){
$db_data = new Category;
// 利用模型将数据传到数据库
$db_data -> name = $data['name'];
$db_data -> parent_ = $data['parent_'];
error_reporting(E_ERROR | E_WARNING | E_PARSE);
if($data['parent_'][0]){
$db_data -> parent0 = $data['parent_'][0];
}else{
$data['parent_'][0] = '0';$db_data -> parent0 = $data['parent_'][0];}
if($data['parent_'][1]){
$db_data -> parent1 = $data['parent_'][1];
}else{
$data['parent_'][1] = '0';$db_data -> parent1 = $data['parent_'][1];}
if($data['parent_'][2]){
$db_data -> parent2 = $data['parent_'][2];
}else{
$data['parent_'][2] = '0';$db_data -> parent2 = $data['parent_'][2];}
if($data['parent_'][3]){
$db_data -> parent3 = $data['parent_'][3];
}else{
$data['parent_'][3] = '0';$db_data -> parent3 = $data['parent_'][3];}
if($data['parent_'][4]){
$db_data -> parent4 = $data['parent_'][4];
}else{
$data['parent_'][4] = '0';$db_data -> parent4 = $data['parent_'][4];}
$db_data -> save();
// 返回结果
return '新增数据成功';
}
}
public function findall(){
// 接收数据
$data = request() -> param();
// return $data;
// 根据模型名判断
if($data['model'] == "admin"){
$db_data = Admin::select();
// 返回查找到的数据
return $db_data;
}else if($data['model'] == "category"){
$db_data = Category::with(['parent0','parent1','parent2','parent3','parent4'])->select()->toArray();
for($i = 0; $i<sizeof($db_data); $i++){
$db_data[$i]['parent'] = array();
if(is_array($db_data[$i]['parent0'])){
array_push($db_data[$i]['parent'], $db_data[$i]['parent0']);
}
if(is_array($db_data[$i]['parent1'])){
array_push($db_data[$i]['parent'], $db_data[$i]['parent1']);
}
if(is_array($db_data[$i]['parent2'])){
array_push($db_data[$i]['parent'], $db_data[$i]['parent2']);
}
if(is_array($db_data[$i]['parent3'])){
array_push($db_data[$i]['parent'], $db_data[$i]['parent3']);
}
if(is_array($db_data[$i]['parent4'])){
array_push($db_data[$i]['parent'], $db_data[$i]['parent4']);
}
}
// $db_data['parent'] = explode($db_data[$i]['parent0'],$db_data[$i]['parent1'],$db_data[$i]['parent2'],$db_data[$i]['parent3'],$db_data[$i]['parent4']);
return json($db_data);
}
}
public function find()
{
// 获取前端传值
$data = request() -> param();
if($data['model'] == "admin"){
$db_data = Admin::find($data['id']);
// 返回查找到的数据
return $db_data;
}else if($data['model'] == "category"){
$db_data = Category::with(['parent0','parent1','parent2','parent3','parent4'])->find($data['id'])->toArray();
$db_data['parent'] = array();
if(is_array($db_data['parent0'])){
array_push($db_data['parent'], $db_data['parent0']);
}
if(is_array($db_data['parent1'])){
array_push($db_data['parent'], $db_data['parent1']);
}
if(is_array($db_data['parent2'])){
array_push($db_data['parent'], $db_data['parent2']);
}
if(is_array($db_data['parent3'])){
array_push($db_data['parent'], $db_data['parent3']);
}
if(is_array($db_data['parent4'])){
array_push($db_data['parent'], $db_data['parent4']);
}
// 返回查找到的数据
return $db_data;
}
}
public function update()
{
// 获取前端传值
$data = request() -> param();
// return $data;
if($data['model'] == "admin"){
// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 静态方法直接更新
$db_data = Admin::update($data, ['id' => $data['id']]);
return '修改数据成功';
}else if($data['model'] == "category"){
error_reporting(E_ERROR | E_WARNING | E_PARSE);
$db_data = Category::where('id', $data['id'])->find();
// 利用模型将数据传到数据库
$db_data -> name = $data['name'];
$db_data -> content = $data['content'];
$db_data -> parent_ = $data['parent_'];
if($data['parent_'][0]){
$db_data -> parent0 = $data['parent_'][0];
}else{
$data['parent_'][0] = '0';$db_data -> parent0 = $data['parent_'][0];}
if($data['parent_'][1]){
$db_data -> parent1 = $data['parent_'][1];
}else{
$data['parent_'][1] = '0';$db_data -> parent1 = $data['parent_'][1];}
if($data['parent_'][2]){
$db_data -> parent2 = $data['parent_'][2];
}else{
$data['parent_'][2] = '0';$db_data -> parent2 = $data['parent_'][2];}
if($data['parent_'][3]){
$db_data -> parent3 = $data['parent_'][3];
}else{
$data['parent_'][3] = '0';$db_data -> parent3 = $data['parent_'][3];}
if($data['parent_'][4]){
$db_data -> parent4 = $data['parent_'][4];
}else{
$data['parent_'][4] = '0';$db_data -> parent4 = $data['parent_'][4];}
// 静态方法直接更新
$db_data -> save();
return '修改数据成功';
}
}
public function delete()
{
// 获取前端传值
$data = request() -> param();
if($data['model'] == "admin"){
$db_data = Admin::find($data['id']);
$db_data->delete();
return '删除数据成功';
}else if($data['model'] == "category"){
$db_data = Category::find($data['id']);
$db_data->delete();
return '删除数据成功';
}
}
}
CategorySet.vue:
<template>
<div>
<h1>{
{ id ? "编辑" : "创建" }}分类h1>
<el-form
label-width="80px"
style="margin-top: 20px"
@submit.native.prevent="save"
>
<el-form-item label="上级分类">
<el-select v-model="model.parent_" multiple>
<el-option
v-for="item in parentOptions"
:key="item.id"
:label="item.name"
:value="item.id"
>el-option>
el-select>
el-form-item>
<el-form-item label="分类名">
<el-input v-model="model.name">el-input>
el-form-item>
<el-form-item>
<el-button type="primary" native-type="submit">保存el-button>
el-form-item>
el-form>
div>
template>
<script>
export default {
props: {
id: {
},
},
data() {
return {
model: {
parent_: []
},
parentOptions: [],
};
},
methods: {
async save() {
let res;
if (this.id) {
res = await this.$http.put("rest/category/" + this.id, this.model);
} else {
res = await this.$http.post("rest/category", this.model);
}
console.log("en?", res);
this.$router.push("/categories/list");
this.$message({
type: "success",
message: "保存成功",
});
},
async fetch() {
const res = await this.$http.get("rest/category/" + this.id);
this.model = res.data;
},
async fetchParentOptions() {
const res = await this.$http.get("rest/category");
this.parentOptions = res.data;
},
},
created() {
this.id && this.fetch();
this.fetchParentOptions();
},
};
script>
CategoryList.vue:
<template>
<div>
<h1>分类列表h1>
<el-table :data="items">
<el-table-column prop="id" label="ID" width="220">
el-table-column>
<el-table-column prop="parent[0].name,parent[1].name,parent[2].name,parent[3].name,parent[4].name" label="上级分类" width="220">
<template slot-scope="scope"> {
{scope.row.parent[0].name}} {
{scope.row.parent[1].name}} {
{scope.row.parent[2].name}} {
{scope.row.parent[3].name}} {
{scope.row.parent[4].name}} template>
el-table-column>
<el-table-column prop="name" label="分类名称">
el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button type="text" size="small" @click="$router.push('/categories/edit/' + scope.row.id)">编辑el-button>
<el-button @click="remove(scope.row)" type="text" size="small">删除el-button>
template>
el-table-column>
el-table>
div>
template>
<script>
export default {
data() {
return {
items: []
}
},
methods: {
async fetch(){
const res = await this.$http.get('rest/category')
this.items = res.data
},
remove(row){
this.$confirm('是否确定要删除分类"' + row.name + '"?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
// 要想使用await,函数必须使用async
// await异步执行,待调用接口获取数据完成后再将值传给res,进行下一步操作
const res = await this.$http.delete('rest/category/' + row.id)
this.$message({
type: 'success',
message: '删除成功!'
});
if(res.status == 200){
// 接口调用成功后,刷新页面
this.fetch()
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
created() {
this.fetch()
}
}
script>
更多设计、功能的学习经验,大家也可以去我的公众号查看!
————