VueJS 3 + Strapi CRUD示例代码

文章目录
  1. 1. Strapi服务端
  2. 2. VueJS3前端
  3. 2.1 variables.js
  4. 2.2 app.js
  5. 2.3 index.html
  6. 2.4 components/departments.js
  7. 2.5 components/employee.js
  8. 2.6 components/home.js
  9. 3 图片上传的思路

这两天刚上手VueJS3,这篇内容是我用Vue3做前端,Strapi做服务端写的前后端分离的基础CRUD的功能实现。

运行界面
修改数据界面

实现了部门和人员管理的功能,主要特性:

  1. 支持数据的增删改查
  2. 头像图片的上传
  3. BootStrap5框架+Modal组件的应用
  4. 使用Axios做http请求

1. Strapi服务端

数据服务端 Backend

首先,建两个Content-Type,分别是Department和Employee,字段设置如下:

Department

Employee

注意Employee中的department字段需要和Department建立关联

至此,服务端就到这里,不做过多阐述。

2. VueJS3前端

前端 Frontend

前端主要由Vuejs3完成,文件结构如下:

下面重点是核心文件

2.1 variables.js

variables.js - 存放服务端api接口的地址

const variables = {
    API_URL: 'http://localhost:4000/',
    PHOTO_URL: 'http://localhost:4000',
}

这里有待优化,因为开始我把所有的API_URL调用后面直接加上对应的content-type,没带/,比如axios请求departments只需要 API_URL + “departments”,然而Strapi图片地址的数据前面都带了/,如果继续使用API_URL会多一个斜杠,实际地址变成http://server//upload/xxx.jpg出现404错误,懒得改了,直接用了新的PHOTO_URL来解决这个问题。

2.2 app.js

app.js - Vue3路由配置文件

const routes = [
    {path: '/home', component: home},
    {path: '/employee', component: employee},
    {path: '/department', component: department}
]

const router = new VueRouter.createRouter({
    history: VueRouter.createWebHashHistory(),
    routes,
})

const app = Vue.createApp({})
// Make sure to _use_ the router instance to make the
// whole app router-aware.
app.use(router)

app.mount('#app')

2.3 index.html

index.html - APP启动入口文件

<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta name="description" content="" />
    <meta name="author" content="" />
    <title>Page title</title>
    <link href="./public/css/styles.css" rel="stylesheet" />
    <link rel="icon" type="image/x-icon" href="./public/assets/img/favicon.png" />
    <script data-search-pseudo-elements defer src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.24.1/feather.min.js" crossorigin="anonymous"></script>
</head>
<body>
    <div id="app" class="container">
        <h3 class="d-flex justify-content-center">
            Vue JS front End
        </h3>
        <h5 class="d-flex justify-content-center">
            Employee Management Portal
        </h5> 
        
        <nav class="navbar navbar-expand-sm bg-light navbar-dark">
            <ul class="navbar-nav">
                <li class="nav-item m-1">
                    <router-link class="btn btn-light btn-outline-primary" to="/home">Home</router-link>
                </li>
                <li class="nav-item m-1">
                    <router-link class="btn btn-light btn-outline-primary" to="/department">Department</router-link>
                </li>
                <li class="nav-item m-1">
                    <router-link class="btn btn-light btn-outline-primary" to="/employee">Employee</router-link>
                </li>
            </ul>
        </nav>

        <router-view></router-view>

    </div>

    <script src="variables.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4"></script>
    <script src="components/home.js"></script>
    <script src="components/department.js"></script>
    <script src="components/employee.js"></script>
    <script src="app.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
    <script src="./public/js/scripts.js"></script>
</body>
</html>

2.4 components/departments.js

组件部分

departments.js - 部门管理的实现

const department={
    template:
    /*html*/
    `
    <div>

    <button type="button" class="btn btn-primary m-2 float-end" data-bs-toggle="modal" 
    data-bs-target="#exampleModal" @click="addClick()">Add Department</button>
    <table class="table table-striped">
        <thead>
            <tr>
                <th>
                    DepartmentId
                </th>
                <th>
                    DepartmentName
                </th>
                <th>
                    Options
                </th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="dep in departments">
                <td>{{ dep.id }}</td>
                <td>{{ dep.name }}</td>
                <td>
                    <button type="button" class="btn bt-light mr-1"  data-bs-toggle="modal" 
                        data-bs-target="#exampleModal" @click="editClick(dep)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
                        <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
                        <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
                        </svg>
                    </button>
                    <button type="button" class="btn bt-light mr-1" @click="deleteClick(dep.id)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
                        <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
                        <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
                        </svg>
                    </button>
                </td>
            </tr>
        </tbody>
    </table>

    <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel"
        aria-hidden="true">
        <div class="modal-dialog modal-lg modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="exampleModalLabel">{{ modalTitle }}</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"
                        aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="input-group mb-3">
                        <span class="input-group-text">Department Name</span>
                        <input type="text" class="form-control" v-model="name">
                    </div>
                    <button type="button" @click="createClick()" 
                    v-if="id==0" class="btn btn-primary">
                    Create
                    </button>
                    <button type="button" @click="updateClick()" 
                    v-if="id!=0" class="btn btn-primary">
                    Update
                    </button>
                </div>
            </div>
        </div>
    </div>

    </div>
    
    `,

    data(){
        return {
            departments: [],
            modalTitle: "",
            name: "",
            id: 0
        }
    },

    methods: {
        refreshData(){
            axios.get(variables.API_URL + 'departments')
            .then((response) => {
                this.departments = response.data;
            });
        },
        addClick(){ 
            this.modalTitle = "Add Department";
            this.id = 0;
            this.name = "";
        },
        editClick(dep){ 
            this.modalTitle = "Edit Department";
            this.id = dep.id;
            this.name = dep.name; 
        },
        createClick(){
            axios.post(variables.API_URL + 'departments',{
                name: this.name
            })
            .then((response) => {
                this.refreshData();
                alert(response.statusText);
                //console.log(response);
            });
        },
        updateClick(){
            axios.put(variables.API_URL + 'departments/' + this.id,{
                name: this.name
            })
            .then((response) => {
                this.refreshData();
                alert(response.statusText);
                //console.log(response);
            });
        },
        deleteClick(id){
            if(!confirm("Are you sure?")){
                return;
            }
            axios.delete(variables.API_URL + 'departments/' + id,{
                name: this.name
            })
            .then((response) => {
                this.refreshData();
                alert(response.statusText);
                //console.log(response);
            });
        },
    },

    mounted() {
        this.refreshData();
    },
}

2.5 components/employee.js

employee.js - 人员管理的实现

const employee={
    template:
    /*html*/
    `
    <div>

    <button type="button" class="btn btn-primary m-2 float-end" data-bs-toggle="modal" 
    data-bs-target="#exampleModal" @click="addClick()">Add Employee</button>
    <table class="table table-striped">
        <thead>
            <tr>
                <th>
                    Employee Id
                </th>
                <th>
                    Employee Name
                </th>
                <th>
                    Department
                </th>
                <th>
                    DOJ
                </th>
                <th>
                    Options
                </th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="emp in employees">
                <td>{{ emp.id }}</td>
                <td>{{ emp.name }}</td>
                <td>{{ emp.department.name }}</td>
                <td>{{ emp.DOJ }}</td>
                <td>
                    <button type="button" class="btn bt-light mr-1"  data-bs-toggle="modal" 
                        data-bs-target="#exampleModal" @click="editClick(emp)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
                        <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
                        <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
                        </svg>
                    </button>
                    <button type="button" class="btn bt-light mr-1" @click="deleteClick(emp.id)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
                        <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
                        <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
                        </svg>
                    </button>
                </td>
            </tr>
        </tbody>
    </table>

    <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel"
        aria-hidden="true">
        <div class="modal-dialog modal-lg modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="exampleModalLabel">{{ modalTitle }}</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"
                        aria-label="Close" id="btnCloseModal"></button>
                </div>
                <div class="modal-body">

                    <div class="d-flex flex-row bd-highlight mb-3">
                        <div class="p-2 w-50 bd-highlight">
                            <div class="input-group mb-3">
                                <span class="input-group-text">Name</span>
                                <input type="text" class="form-control" v-model="name">
                            </div>
                            <div class="input-group mb-3">
                                <span class="input-group-text">Department</span>
                                <select class="form-select" v-model="department">
                                    <option v-for="dep in departments" :value="dep.id" :key="dep.id" :selected="dep.id == this.id">
                                        {{ dep.name }}
                                    </option>
                                </select>
                            </div>

                            <div class="input-group mb-3">
                                <span class="input-group-text">DOJ</span>
                                <input type="date" class="form-control" v-model="DateOfJoining">
                            </div>

                        </div>

                        <div class="p-2 w-50 bd-highlight">
                            <img width="250" height="250" :src="profile" />
                            <input class="m-2" type="file" @change="imageUpload" id="ProfileUpload">
                        </div>
                    </div>

                    <button type="button" @click="createClick()" 
                    v-if="id==0" class="btn btn-primary">
                    Create
                    </button>
                    <button type="button" @click="updateClick()" 
                    v-if="id!=0" class="btn btn-primary">
                    Update
                    </button>
                    

                </div>
            </div>
        </div>
    </div>

    </div>
    
    `,

    data(){
        return {
            departments: [],
            employees: [],
            modalTitle: "",
            name: "",
            id: 0,
            department: "",
            DateOfJoining: "",
            profile: variables.PHOTO_URL + "/uploads/profile_1bfe3b2ecc.jpg",
            PHOTO_SERVER: variables.PHOTO_URL,
            imgId: 0,
        }
    },

    methods: {
        refreshData(){
            axios.get(variables.API_URL + 'employees')
            .then((response) => {
                this.employees = response.data;
                //console.log(response.data);
            });
            axios.get(variables.API_URL + 'departments')
            .then((response) => {
                this.departments = response.data;
            });
        },
        addClick(){ 
            this.modalTitle = "Add Employee";
            this.id = 0;
            this.name = "";
            this.department = "";
            this.DateOfJoining = "";
            this.profile = variables.PHOTO_URL + "/uploads/profile_1bfe3b2ecc.jpg";
        },
        editClick(emp){ 
            this.modalTitle = "Edit Employee";
            this.id = emp.id;
            this.name = emp.name;
            this.department = emp.department.id;
            this.DateOfJoining = emp.DOJ;
            this.profile =  (emp.profile) ? this.PHOTO_SERVER +  emp.profile.url : this.PHOTO_SERVER + "/uploads/profile_1bfe3b2ecc.jpg";
        },
        createClick(){
            axios.post(variables.API_URL + 'employees',{
                name: this.name,
                department: this.department,
                DOJ: this.DateOfJoining,
            })
            .then((response) => {
                this.refreshData();
                alert(response.statusText);
                //console.log(this.imgId);
                
                axios.put(variables.API_URL + "employees/" + response.data.id, {profile: this.imgId}).then((res)=>{
                    //handle success
                    console.log(res.data);
                }).catch((err)=>{
                    //handle error
                })
                //console.log(response.data);

                //CLOSE MODAL
                let btnClose = document.querySelector('#btnCloseModal');
                btnClose.click();
            });
        },
        updateClick(){
            axios.put(variables.API_URL + 'employees/' + this.id,{
                name: this.name,
                department: this.department,
                DOJ: this.DateOfJoining,
                //profile: this.PhotoFileName
            })
            .then((response) => {
                this.refreshData();
                alert(response.statusText);
                //console.log(response);
            });
            //CLOSE MODAL
            let btnClose = document.querySelector('#btnCloseModal');
            btnClose.click();
        },
        deleteClick(id){
            if(!confirm("Are you sure?")){
                return;
            }
            axios.delete(variables.API_URL + 'employees/' + id,{
                name: this.name
            })
            .then((response) => {
                this.refreshData();
                alert(response.statusText);
                //console.log(response);
            });
        },
        imageUpload(){
            let formData = new FormData();
            let imageFile = document.querySelector('#ProfileUpload');
            formData.append('files', imageFile.files[0]);
            axios.post(
                variables.API_URL + "upload/",
                formData)
                .then((response)=>{
                    this.profile = this.PHOTO_SERVER + response.data[0].url;
                    console.log(response);

                    //Link image to a content type
                    const imageId = response.data[0].id;
                    this.imgId = imageId;
                    if(!this.id) return;
                    axios.put(variables.API_URL + "employees/" + this.id, {profile: imageId}).then((res)=>{
                        //handle success
                        //console.log(res.data);
                    }).catch((err)=>{
                        //handle error
                    })

                }).catch((error)=>{
                    //handle error
                    console.log(error);
            })
            
        }
    },

    mounted() {
        this.refreshData();
    },
}

2.6 components/home.js

home.js - 首页文件,未加工

const home={
    template:
    /*html*/
    `
    <h1>This is Home</h1>
    `
}

3 图片上传的思路

关于Strapi图片上传的处理方法及注意事项

1.图片统一post请求发送到http://server/upload/地址中,而不是对应content-typemedia字段中

2.上传完成后需要拿到image的ID

3.在创建或更新的content-type的数据中建立media字段的对应连接,使用的是put方法,提交的数据格式为{mediaFieldName: [id]},没错只需要提交ID

Post Comment