目录
1.1、添加产品(/adminapi/product/add)
1.2、产品列表(/adminapi/product/list)
1.3、编辑产品(/adminapi/product/update)
前言:
前端项目git地址:Enterprise Product Management System: 企业产品管理系统:web端展示、后台进行添加信息,前端用的vue3+vuex。
后端项目git地址:Enterprise Product Management System Backend: 企业产品管理系统:后端项目,用的node +mysql
查看git记录:"第一版"是只有后台管理+admin/web分离的接口,"第二版"是将前台页面和后台管理系统放在一起了,根据自己需要进行下载与切换。目前是"第二版"。
一、产品模块(products表)
1.1、添加产品(/adminapi/product/add)
后端先创建一个products表
-- 创建products表
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,-- 1、传统自增int主键
title VARCHAR(50) NOT NULL UNIQUE,
introduction TEXT,
detail TEXT,
cover VARCHAR(255),
userId INT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
edit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 2、UUID作为主键
-- id CHAR(36) PRIMARY KEY DEFAULT (UUID())
-- 缺点:不连续,索引性能低于自增int
-- 3、时间戳作为主键
-- 4、带前缀的自增编号(如 NEWS0001、PROD0001)
-- 每个表直接会重复?
-- 插入测试产品数据
INSERT INTO products (title, introduction, detail, cover,userId) VALUES
('标题1','简要描述1','详细描述1','/avatar/admin.jpg', 1),
('标题2','简要描述1','详细描述2','/avatar/editor.jpg', 2)
1.2、产品列表(/adminapi/product/list)
1.3、编辑产品(/adminapi/product/update)
前三个接口几乎可以照搬vue3+node.js+mysql写接口(一)_node.js mysql vue-CSDN博客,除了字段不一样。
1.4、首页产品联动
当添加产品成功后,首页就会产生相应的轮播图
<el-carousel
:interval="4000"
type="card"
height="400px"
v-if="loopList.length > 0"
>
<el-carousel-item v-for="item in loopList" :key="item.id">
<div
:style="{
backgroundImage: `url(http://localhost:3000${item.cover})`,
backgroundSize: 'cover',
height: '400px',
}"
>
<h3>{{ item.title }}</h3>
</div>
</el-carousel-item>
</el-carousel>
<style lang="scss" scoped>
.el-carousel__item h3 {
color: white;
opacity: 0.75;
line-height: 200px;
margin: 0;
text-align: center;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
</style>
二、前台模块
这里介绍的是单独开发一个项目,而不是和后台管理放在一起的。
2.1、路由配置
import { createRouter, createWebHashHistory } from "vue-router";
import Home from "../views/Home.vue";
import News from "../views/News.vue";
import NewsDetails from "../views/NewsDetails";
import Product from "../views/Product.vue";
const routes = [
{
path: "/",
name: "home",
component: Home,
},
{
path: "/news",
name: "news",
component: News,
},
{
path: "/news-detail/:id",
name: "NewsDetails",
component: NewsDetails,
},
{
path: "/product",
name: "product",
component: Product,
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
2.2、NavBar组件
<template>
<div class="navbar">
<el-menu
:default-active="route.fullPath"
class="el-menu-demo"
mode="horizontal"
router
>
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/news">新闻中心</el-menu-item>
<el-menu-item index="/product">产品与服务</el-menu-item>
<el-menu-item index="" @click="handleClick">登录</el-menu-item>
</el-menu>
<div class="right">企业门户管理系统</div>
</div>
</template>
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
const handleClick = () => {
window.location = "http://localhost:8081/";
};
</script>
<style lang="scss" scoped>
.navbar {
position: sticky;
top: 0px;
z-index: 999;
}
.right {
position: fixed;
top: 0;
right: 20px;
width: 160px;
height: 59px;
line-height: 59px;
text-align: center;
}
</style>
2.3、新闻搜索
<div class="search-center">
<el-popover
title="检索结果"
placement="bottom"
width="50%"
:visible="popVisible"
>
<template #reference>
<el-input
v-model="searhText"
placeholder="请输入新闻关键字"
:prefix-icon="Search"
type="search"
size="large"
@input="popVisible = true"
@blur="popVisible = false"
/></template>
<div v-if="newsListFilter.length">
<div
v-for="(data, index) in newsListFilter"
:key="data.id"
class="search-item"
@click="handleClick(data.id)"
>
{{ index + 1 }} . {{ data.title }}
</div>
</div>
<div v-else>
<el-empty description="暂无新闻" :image-size="50" />
</div>
</el-popover>
</div>
// 根据搜索内容过滤
const newsListFilter = computed(() => {
return newsList.value.filter((item) => {
return item.title.includes(searhText.value);
});
});
2.4、新闻选项卡
<el-tabs v-model="whichTab" style="margin: 20px">
<el-tab-pane
v-for="item in options"
:key="item.value"
:label="item.label"
:name="item.value"
>
<el-row :gutter="20">
<el-col :span="18">
<div
v-for="data in tabnews[item.value]"
:key="data.id"
style="padding: 10px"
>
<el-card
:body-style="{ padding: '0px' }"
shadow="hover"
@click="handleClick(data.id)"
>
<div
class="tab-image"
:style="{
backgroundImage: `url(http://localhost:3000${data.cover})`,
}"
></div>
<div style="padding: 14px; float: left">
<span>{{ data.title }}</span>
<div class="edit-time">
<span>{{ formatTime.getTime(data.edit_time) }}</span>
</div>
</div>
</el-card>
</div>
</el-col>
<el-col :span="6">
<el-timeline style="padding-top: 16px">
<el-timeline-item
v-for="data in tabnews[item.value]"
:key="data.id"
:timestamp="formatTime.getTime(data.edit_time)"
>
{{ data.title }}
</el-timeline-item>
</el-timeline>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
借助lodash组装数组,将其分为三类(后台创建时的三类选项)
import _ from "lodash";//npm i lodash
// 组装选项卡数据
// groupBy用法:https://www.lodashjs.com/docs/lodash.groupBy
const tabnews = computed(() =>
_.groupBy(newsList.value, (item) => item.category)
);
2.5、新闻详情
点击右侧新闻,此时左侧内容更新:这里相当于自己跳转到自己,onMounted只会触发一次,需要监听路由变化的话,需要使用watchEffect
const stop = watchEffect(async () => {
if (!route.params.id) return;
const res = await axios.get(`/adminapi/news/list/${route.params.id}`);
const res1 = await axios.get(`/adminapi/news/topList?limit=4`); //可以用vuex存储主页的;也可以由后端返回最新的前四个数据
currentNews.value = res.data[0];
topNews.value = res1.data;
});
// 页面销毁时取消监听
onBeforeUnmount(() => {
stop();
});
const router = useRouter();
// 跳转详情
// 注意:这里相当于自己跳转到自己,onMounted只会触发一次,需要监听路由变化的话,需要使用watchEffect
const handleClick = (id) => {
router.push(`/news-detail/${id}`);
};
2.6、产品与服务
其实就是将后端的数据返回,前端根据指定样式渲染即可
三、总结
后端:
post请求参数:用body接收;
get请求参数:接收URL路径中的参数用params接收、查询字符串参数用query接收
前端:
1、model属性:让表单的验证功能正常工作,再结合rules+prop
2、如果表单form是ref或者reactive对象,直接赋值,会改变原始数据tableData,需要深拷贝
3、文件上传时,前端拿到的是file文件【二进制】格式,传给后端时一般用FormData形式,此时需要改变消息头:multipart/form-data
4、当一个页面自己跳转到自己,onMounted只会触发一次,需要监听路由变化的话,需要使用watchEffect,在页面销毁时取消监听。