#### 技能学习:学习使用php(tp6框架) + vue.js,开发前端全栈网站-7.分类的模型关联和通用CRUD接口
> 技能学习:学习使用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编辑器),并批量上传图片
###### 1.制作通用CRUD接口
分类内容的接口与管理员接口相同,都是增删改查,如果每个接口都复制一遍再稍作改动,接口页面内容会太多。所以我们对两个接口进行比对,找不同:
![](https://img.kancloud.cn/ba/5d/ba5da95a883871c492cd767d575b847d_628x714.png)
只有这里不同,所以我们只要在路由中把这个辨别值改为动态即可:
![](https://img.kancloud.cn/fe/f8/fef801600a7a245bb9db479a6f690aaf_584x484.png)
将这个辨别值当作模型名处理,因为后续数据接口操作实际上就是模型不同,增删改查方法都一样。
接口函数接收模型名(以查找所有数据接口为例):
~~~
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语言以命名空间为主,所以无法直接使用传值做模型方法使用,故我们需要判断后再用模型方法进行数据操作。
测试原管理员接口:
![](https://img.kancloud.cn/38/bd/38bd0b9cef187fd298f14ae129e2565c_1440x493.png)
没问题,仿照管理员功能做出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模型:
~~~
<?php
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);
}
}
~~~
新建数据表:
![](https://img.kancloud.cn/af/b8/afb8e443dc90a9e8af491a396b79c638_904x492.png)
![](https://img.kancloud.cn/a9/b1/a9b106ab94e7205bc09d048b9dd08337_611x162.png)
刷新页面查看:
![](https://img.kancloud.cn/94/c9/94c9cb63b511d97aa896c20274a261e2_1440x912.png)
没有报错,应该成功了,下面我们将全部接口函数改造:
~~~
<?php
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 '删除数据成功';
}
}
}
~~~
新增数据测试:
![](https://img.kancloud.cn/1b/41/1b41aae2b37237425da635fe7f494d63_1440x493.png)
修改测试:
![](https://img.kancloud.cn/a6/41/a641ddd6ac03278fcdf093973e5e0176_1440x493.png)
成功,都没问题,到此通用接口完成。
###### 2.无限层级分类
(1)更改新建分类页面
CategorySet.vue设置上级分类parent,位于分类名之上:
~~~
<el-form-item label="上级分类">
<el-select v-model="model.parent">
<!-- 使用select获取分类名name和该分类的id,后期如果修改分类名自动更新子分类的上级分类 -->
<!-- 其中label获取分类名,发送到数据库的值为该分类的id————以id为分类寻找依据 -->
<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
}
~~~
![](https://img.kancloud.cn/9a/84/9a8473e6d89d12ed142bd29395647256_1051x677.png)
此时页面已接收到分类信息,并可以显示分类名了。
![](https://img.kancloud.cn/69/3b/693bd6638960942e7f6e42c777c3e425_1268x353.png)
![](https://img.kancloud.cn/b4/8a/b48aaca3e309779d8c0aaf0a516a96e4_690x493.png)
因为是直接复制的,所以还是用户名,改一下:
![](https://img.kancloud.cn/c8/7f/c87fe23e44213615655f5db4c3d4732f_422x113.png)
修改添加数据接口,添加parent字段:
![](https://img.kancloud.cn/75/b2/75b2002174e507cae9cc808a0088ba7a_703x532.png)
此时新建分类:
![](https://img.kancloud.cn/5f/57/5f5766194935516d7fa28bc8a8d2b4c3_655x314.png)
![](https://img.kancloud.cn/af/20/af207bc5bf574e59058c8b5f31ed71e0_1440x493.png)
成功:
![](https://img.kancloud.cn/16/16/16169d7e89759313a4c55156ba6f5935_1440x493.png)
这时候修改列表页,将上级分类显示出来:
![](https://img.kancloud.cn/8c/7a/8c7a82f600f43bd9bba5dc0ebefa06f0_900x290.png)
保存后页面将显示上级分类:
![](https://img.kancloud.cn/3b/d9/3bd9c20aebaca24bf745275fb3756ac8_688x232.png)
此时我们需要让上级分类以分类名形式显示。
修改查询所有内容接口:
![](https://img.kancloud.cn/d8/6f/d86f2d5cd1b38e4315c5b55cdb56d624_572x337.png)
此时刷新页面得到关联分类内容:
![](https://img.kancloud.cn/68/4f/684f75b56cbab5ce92fef20e3e87f257_1440x912.png)
但这并不是我们需要的内容,我们要的是上级分类,但是现在给我们查找的是上级分类的所有下级分类,所以再次改动,添加反向查询父级的方法:
![](https://img.kancloud.cn/dd/09/dd09e7af07b7de0a703e8cc52981d865_617x511.png)
改动find\_all接口函数:
![](https://img.kancloud.cn/a2/fa/a2fae216da2d0e6dcc9a4f54ac4e8f63_606x340.png)
刷新页面测试:
![](https://img.kancloud.cn/d7/d9/d7d9bcdb0c4e40afff093049e280ce0e_704x240.png)
成功查询到上级分类数据。
修改前端页面:
![](https://img.kancloud.cn/c0/46/c0463f4040157823f786a5e69687d1be_742x297.png)
保存查看页面:
![](https://img.kancloud.cn/3c/7d/3c7d5129a580fdc47c69a0e99e878cb4_768x310.png)
成功。很好。
此时我们再次修改上级分类php1改回php:
![](https://img.kancloud.cn/13/52/135276461c91f93943b828b318e20db6_1440x493.png)
由于是id关联,所以上级分类变动后名字一起变化。
###### 3.关联上级多个分类
此时我们再创建一个vue分类:
![](https://img.kancloud.cn/73/cf/73cf72fc4c538080cfeb31216027acc1_764x315.png)
居然报错,看来我们之前的代码是将parent当作了必填项?找一下原因:
经过排查,是前端的原因,parent进入页面时被parentOptions: \[\]定义成了数组,所以如果为空的话就是字符串格式。
所以我们需要在数据模型中提前让parent定义成数组格式:
![](https://img.kancloud.cn/97/ef/97ef40bba712d543f853b709275e859a_363x191.png)
保存再次测试:
![](https://img.kancloud.cn/1e/bc/1ebcbb7bda9cbc2eac020dec22b22bc5_1440x493.png)
没问题,下面正式开始多个上级分类关联,例如加一个分类名为vue.js+tp6全栈开发的题目,上级分类为vue.js和tp6。
只需要修改前端页面,在下拉框中加入一个属性multiple:
![](https://img.kancloud.cn/63/32/63328668343b99cbc84b58859fbae146_810x349.png)
又因为我们上一步已经将parent改为数组格式,所以可以放心上传。
测试:
![](https://img.kancloud.cn/62/e8/62e8de9c235e3515bc58b3b5b2d9a4a0_518x345.png)
![](https://img.kancloud.cn/f2/aa/f2aa3dc37f050fa012ae9d0cb8b40f34_1440x493.png)
虽然成功了,但是列表页一个都没有显示,查找原因:
然后我试了一下午,都没有成功,摊牌了,我不会,大神教教我!
经过又半天的研究,我转变了思路,部分有些复杂,数据表字段也变了一些,给大家展示下,大家自行研究一段时间研究不出来可以参照一下:
下篇文章学习图片的上传。
Category模型:
~~~
<?php
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');
}
}
~~~
接口函数:
~~~
<?php
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>
<!-- 使用select获取分类名name和该分类的id,后期如果修改分类名自动更新子分类的上级分类 -->
<!-- 其中label获取分类名,发送到数据库的值为该分类的id————以id为分类寻找依据 -->
<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>
~~~
结果页面:
![](https://img.kancloud.cn/e2/71/e27122df2c55e441735e9c477eb2a648_1306x634.png)
![](https://img.kancloud.cn/ae/64/ae642064adf0fefc479940e437a3f900_1306x634.png)
- tp6+vue
- 1.工具和本地环境
- 2.启动项目
- 3.路由、模型与数据库操作
- 4.优化后端接口,前端使用axios实现接口功能
- 5.用户登录,bcrypt(hash)加密与验证
- 6.用户登录(二),token验证
- 7.分类的模型关联和通用CRUD接口
- 8.使用vue的markdown编辑器并批量上传图片
- Node.js + Vue.js
- 工具,本地环境
- 2.1启动项目
- 3.element-ui和vue-router路由的安装和使用
- 4.使用axios,并创建接口上传数据到mongodb数据库
- 5.mongoodb数据库的“删、改、查”操作
- 6.mongodb数据库无限层级的数据关联(子分类)
- 7.使用mongodb数据库关联多个分类(关联多个数据)
- 8.server端使用通用CRUD接口
- 9.图片上传
- 10.vue的富文本编辑器(vue2-editor)
- 11.动态添加分栏上传多组数据
- 12-1.管理员模块
- 13-1.搭建前台web端页面
- 1.使用sass工具搭建前台web端页面
- 2.sass工具的变量
- 3.使用flex布局并开始搭建web端
- 4.vue广告轮播图,并使用接口引入数据
- 5.使用字体图标(iconfont)
- 6.卡片组件的封装
- 14-1.生产环境编译
- 1.环境编译
- 2.购买域名服务器并解析域名到服务器
- 3.nginx配置web服务器并安装网站环境
- 4.git拉取代码到服务器
- 5.配置Nginx反向代理
- 6.迁移本地数据到服务器(mongodump)
- uni
- 1.工具&本地环境
- 2.页面制作
- 3.页面制作、组件与轮播
- 4.页面跳转与横向滑动
- 5.用户授权登录和用户信息获取
- 6.用户注册和数据存储
- 7.用户填写表单信息