1.API接口代码:tp\app\api\controller\Api.php
<?php
namespace app\api\controller;
use think\facade\Db;
use think\facade\Request;
class Api{
// Access to XMLHttpRequest at 'http://www.tp.com/index.php/api/Api/index'
// from origin 'http://localhost:8080' has been blocked by CORS policy: No
// 'Access-Control-Allow-Origin' header is present on the requested resource.
// 错误信息:是因为前后端分离导致的。前后端 前端和后端的域名不是一个。不是一个,就会出现这种错误。
// 跨域名访问的安全错误提示
public function __construct(){
// 使用php的header函数,设置为*,全部能访问
header("Access-Control-Allow-Origin:*");
}
public function index(){
// 如何写一个 前端数据接口呢?app、小程序、vue
// 接口:让2个以上的项目进行数据联通,数据交互
// 多个语言 必须有统一的格式, 最后返回值,必须多种语言都能使用
// 接口的统一数据格式是 json
// php有json的函数
// json_encode 把php的数据加密成json格式
// json_decode 把json格式转为 php数据格式
// $arr = [
// "ouyangke" => "欧阳克",
// "miejue" => "灭绝师太",
// "php" => [
// "ouyangke",
// "miejue"
// ],
// "tianpeng" => "朱天蓬"
// ];
// json格式,是文本,我们就可以echo
// {"ouyangke":"\u6b27\u9633\u514b","miejue":"\u706d\u7edd\u5e08\u592a","tianpeng":"\u6731\u5929\u84ec"}
// echo json_encode($arr);
// 1、接口 必须用json返回数据
// 2、用数组 转为json数据
$ad = Db::table('oyk_adver')->where('status',1)->order('sort DESC')->select()->toArray();
$lists = Db::table('oyk_shop_lists')->where('status',1)->order('add_time DESC')->limit(6)->select()->toArray();
// & 取之前的地址
foreach($lists as &$v){
$img = explode(';',$v['img']);
$v['img_s'] = $img[0];
}
// 3、接口只能一次性返回数据,不能多次
// echo json_encode($ad);
// echo json_encode($lists);
$arr = [
'ad' => $ad,
'lists' => $lists
];
echo json_encode($arr);
}
public function goods_index(){
//1.第一种:父子层级获取数据 (更快更好)
// echo '第一种方法开始时间:' . time() . '<br />';
$cat = Db::table('oyk_shop_cat')->where('status',1)->order('pid,sort DESC')->select()->toArray();
// print_r($cat);
$tmp =[];
foreach($cat as $v){
// pid == 0 表示是一级菜单
if($v['pid'] == 0){
//把一级的id,作为下标
$tmp[$v['cid']] = $v;
}else {
//把多个二级放入一级菜单下面
$tmp[$v['pid']]['son'][] = $v;
}
}
// print_r($tmp);
// echo json_encode($tmp);
$arr = [
'code'=>0,
'msg'=>'成功',
// 'data'=>$tmp,
// 第二种显示方式
'data'=>array_merge($tmp),
];
echo json_encode($arr);
//把二级菜单放到一级菜单下面
// foreach($cat as $v){
// if($v['pid'] != 0){
// //把一个二级放入一级菜单下面
// // $tmp[$v['pid']]['son'] = $v;
// //把多个二级放入一级菜单下面
// $tmp[$v['pid']]['son'][] = $v;
// }
// }
// print_r($tmp);
// echo '第一种方法结束时间:' . time() . '<br />';
// 2.第二种方法
// echo '第二种方法开始时间:' . time() . '<br />';
// $cat = Db::table('oyk_shop_cat')->where('status',1)->where('pid',0)->select()->toArray();
// // print_r($cat);
// foreach($cat as &$v){
// $v['son'] = Db::table('oyk_shop_cat')->where('status',1)->where('pid',$v['cid'])->select()->toArray();
// }
// print_r($cat);
// echo '第二种方法结束时间:' . time() . '<br />';
}
// 列表页
public function goods_lists(){
// $id = input('post.id');
$id = input('post.id');
if(empty($id)){
echo json_encode([
'code' => 1,
'msg' => '未找到列表',
]);
exit;
}
$page = input('post.p',1);
$count = Db::table('oyk_shop_lists')->where('status',1)->where('cid',$id)->count();
// print_r((int)($count/5)+1);
// ceil 向上取整
// print_r(ceil($count/5));
$lists = Db::table('oyk_shop_lists')->where('status',1)->page(1,5)->where('cid',$id)->select()->toArray();
// 图集处理获取第一张
foreach($lists as &$v){
$img = explode(';',$v['img']);
$v['img_s'] = $img[0];
}
$arr = [
'code' => 0,
'msg' =>'成功',
'data' => [
'lists' => $lists,
'num' => ceil($count/5),
],
];
echo json_encode($arr);
}
//详情页
public function goods_details(){
$id = input('post.id');
if(empty($id)){
echo json_encode([
'code' =>1,
'msg' =>'未找到商品',
]);
exit;
}
$find = Db::table('oyk_shop_lists')->where('id',$id)->find();
// print_r($find);
if(!empty($find)){
$find['img_s'] = explode(';',$find['img']);
$find['info_s'] = explode(';',$find['info']);
}
echo json_encode([
'code' => 0,
'msg' => '成功',
'data' => $find,
]);
}
}
2.Js获取数据代码:vue\src\network\index.js
import { request } from "./request.js";
export function Index() {
return request({
method: "POST",
url: "Api/index",
});
}
export function GoodsIndex() {
return request({
method: "POST",
url: "Api/goods_index",
});
}
export function GoodsLists(data = {}) {
return request({
method: "POST",
url: "Api/goods_lists",
// 通过data 向Api接口传值
data,
});
}
export function GoodsDetails(data = {}) {
return request({
method: "POST",
url: "Api/goods_details",
// 通过data 向Api接口传值
data,
});
}
3.Index.vue品牌列表渲染代码:vue\src\views\Goods\Index.vue
<template>
<div class="fui-fullHigh-group">
<div class="fui-fullHigh-item menu">
<div
class="nav"
:class="on == index ? 'on' : ''"
@click="edit_cat(index)"
v-for="(item, index, key) in cat"
:key="key"
>
{{ item.name }}
</div>
</div>
<div class="fui-fullHigh-item container">
<!-- 第一种遍历方式 :两次循环
<div
class="fui-icon-group"
v-for="(item, index, key) in cat"
:key="key"
>
<div v-if="on == index">
<router-link to="/goods_lists" v-for="(items, indexs, keys) in item.son" :key="keys">
<div class="fui-icon-col">
<div class="icon">
<img class="img" :src="items.pic" />
</div>
<div class="text">{{ items.name }}</div>
</div>
</router-link>
</div>
</div> -->
<!-- 第二种遍历方式 -->
<div class="fui-icon-group">
<router-link
:to="'/goods_lists?id=' + items.cid"
v-for="(items, indexs, keys) in cat_son"
:key="keys"
>
<div class="fui-icon-col">
<div class="icon">
<img class="img" :src="items.pic" />
</div>
<div class="text">{{ items.name }}</div>
</div>
</router-link>
</div>
</div>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
import { GoodsIndex } from "../../network/index.js";
export default {
setup() {
const data = reactive({
// on: 1,
// 第二种方式
on: 0,
cat: [],
cat_son: [],
});
//切换左侧一级分类
const edit_cat = (e) => {
data.on = e;
console.log(e);
data.cat_son = data.cat[e].son;
};
// 获取接口数据
GoodsIndex().then((e) => {
console.log(e);
if (e.code != 0) {
}
data.cat = e.data;
// 第二种方式
console.log(e.data[0]);
data.cat_son = e.data[0].son;
});
return {
title: "分类",
...toRefs(data),
edit_cat,
};
},
};
</script>
<style scoped>
.fui-fullHigh-group {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
display: flex;
}
/* 左侧 */
.fui-fullHigh-item {
height: inherit;
width: 100%;
background: #fff;
overflow-y: auto;
}
.fui-fullHigh-item.menu {
width: 5.25rem;
background: #f8f8f8;
}
.fui-fullHigh-item.menu .nav {
font-size: 28rpx;
text-align: center;
color: #000;
padding: 0.2rem 0;
height: 2.5rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
position: relative;
display: flex;
justify-content: center;
align-items: Center;
}
.fui-fullHigh-item.menu .on {
background: #fff;
position: relative;
color: #ff5555;
}
/* 右侧 */
.fui-fullHigh-item.container {
position: relative;
padding: 0.8rem;
flex: 1;
}
.fui-icon-group {
position: relative;
overflow: hidden;
background: #fff;
display: flex;
flex-wrap: wrap;
}
.fui-icon-group .fui-icon-col {
height: auto;
position: relative;
padding: 0.5rem 0;
text-align: center;
transition: background-color 300ms;
-webkit-transition: background-color 300ms;
float: left;
border: none !important;
}
.fui-icon-group .fui-icon-col {
float: left;
margin-right: 17px;
padding-top: 10px;
}
.fui-icon-group .fui-icon-col .icon {
height: 115px;
width: 100%;
margin: auto;
text-align: center;
line-height: 2.2rem;
}
.icon {
font-family: "icon" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.img {
width: 115px;
height: 100%;
}
.fui-icon-group .fui-icon-col .text {
font-size: 24px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0.2rem;
color: #000;
}
.fui-icon-group .fui-icon-col .text {
font-size: 0.55rem;
line-height: 1.05rem;
padding-top: 0.5rem;
}
</style>
4.Lists.vue产品列表渲染代码:vue\src\views\Goods\Lists.vue
<template>
<Header :title="title"></Header>
<div style="background: #f3f3f3">
<!-- 商品列表 -->
<div class="fui-content">
<div class="fui-content-inner">
<div class="fui-goods-group block">
<!-- 单个商品 -->
<div
class="fui-goods-item"
@click="go_url(item.id)"
v-for="(item, index, key) in lists"
:key="key"
>
<img class="image" :src="item.img_s" />
<div class="detail">
<div class="name">{{ item.title }}</div>
<div
style="
line-height: 0.7rem;
height: 0.7rem;
color: #b2b2b2;
font-size: 0.6rem;
margin-top: 0.2rem;
text-decoration: line-through;
"
></div>
<div class="price">
<span class="text">¥{{ item.price }}</span>
<span class="buy">销量:{{ item.num }}</span>
</div>
</div>
</div>
<!-- 单个商品 -->
</div>
</div>
<button v-if="is_b" class="ant-btn ant-btn-primary" style="width: 100%" @click="xiala()">
加载更多
</button>
</div>
</div>
</template>
<script>
import Header from "../../components/Header.vue";
import { reactive, toRefs } from "vue";
import { useRoute, useRouter } from "vue-router";
import { GoodsLists } from "../../network/index.js";
export default {
components: {
Header,
},
setup() {
const route = useRoute();
const router = useRouter();
const go_url = (id) => {
router.push("/goods_details?id=" + id);
};
// 获取数据
// console.log(route.query.id);
const data = reactive({
lists: [],
page: 1,
num: 1,
is_b: true,
});
GoodsLists({ id: route.query.id, p: data.page }).then((e) => {
// console.log(e);
data.lists = e.data.lists;
data.num = e.data.num;
// 取掉加载更多
if (data.page >= data.num) {
data.is_b = false;
}
});
// 加载更多
const xiala = () => {
data.page = data.page + 1;
if (data.page <= data.num) {
GoodsLists({ id: route.query.id }).then((e) => {
// console.log(e);
// data.lists = e.data;
//合并显示页
data.lists = data.lists.concat(e.data.lists);
// 取掉加载更多
if (data.page >= data.num) {
data.is_b = false;
}
});
}
};
return {
go_url,
title: "列表",
...toRefs(data),
xiala,
};
},
};
</script>
<style scoped>
/* 顶部筛选 */
.sort {
position: relative;
width: 100%;
padding: 0.4rem 0;
background: #fff;
-webkit-box-align: center;
border-bottom: 1px solid #e7e7e7;
}
.sort .item {
position: relative;
width: 1%;
display: table-cell;
text-align: center;
font-size: 0.7rem;
border-left: 1px solid #e7e7e7;
color: #666;
}
.sort .item:first-child {
border: 0;
}
.on .text {
color: #fd5454;
}
/* 商品 */
.fui-content {
position: absolute;
right: 0;
left: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.fui-content-inner {
box-sizing: border-box;
border-top: 1px solid transparent;
margin-top: -1px;
}
.fui-goods-group {
height: auto;
overflow: hidden;
background: #f9f9f9;
}
.fui-goods-group.block {
padding: 0.2rem;
}
/* 单个商品 */
.fui-goods-group .fui-goods-item {
width: 50%;
float: left;
border-bottom: 0;
background: none;
padding: 0.25rem;
display: block;
}
.fui-goods-item {
position: relative;
height: auto;
padding: 0.8rem;
border-bottom: 1px solid #e7e7e7;
background: #fff;
overflow: hidden;
display: flex;
}
.image {
width: 100%;
overflow: hidden;
margin: 0;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
position: relative;
float: none;
}
/* 商品名 */
.detail {
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
background: #fff;
padding-left: 0.5rem;
padding: 0.5rem;
}
.name {
height: 1.7rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 0.65rem;
line-height: 0.9rem;
margin-top: 0;
color: #262626;
}
.price {
position: relative;
display: flex;
align-items: center;
font-size: 0.7rem;
margin-top: 0;
}
.price .text {
flex: 1;
color: #ff5555;
font-size: 0.8rem;
}
.buy {
text-align: center;
color: #666;
font-size: 0.6rem;
}
.ant-btn {
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
touch-action: manipulation;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: rgba(0, 0, 0, 0.85);
background: #fff;
border-color: #d9d9d9;
}
.ant-btn-primary {
color: #fff;
background: #1890ff;
border-color: #1890ff;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px 0 rgb(0 0 0 / 5%);
}
</style>
4.Details.vue详情页渲染代码:vue\src\views\Goods\Details.vue
<template>
<Header :title="title" :is_img="1"></Header>
<div class="rele-wrap">
<div class="has-bottom">
<swiper
:modules="modules"
:loop="true"
:pagination="swiper_options.pagination"
:autoplay="swiper_options.autoplay"
class="swiper"
>
<swiper-slide v-for="(item, index, key) in shop.img_s" :key="key">
<img :src="item" alt="" class="active" />
</swiper-slide>
</swiper>
<div class="idle-panel">
<div class="p1">
{{ shop.title }}
</div>
<div class="p2">
<text class="span">{{ shop.price }}元</text>
</div>
</div>
<div class="sale-panel">
<div class="tit">商品详情</div>
<div class="all open">
<img :src="item" v-for="(item, index, key) in shop.info_s" :key="key" />
</div>
</div>
</div>
<div class="fui-navbar bottom-buttons">
<button class="btn btn_left">加入购物车</button>
<button class="btn btn_right" @click="go_url()">立即购买</button>
</div>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
import { useRoute, useRouter } from "vue-router";
import Header from "../../components/Header.vue";
import { GoodsDetails } from "../../network/index.js";
import SwiperCore, { Pagination, Autoplay } from "swiper";
import { Swiper, SwiperSlide } from "swiper/vue/swiper-vue.js";
import "swiper/swiper.min.css";
import "swiper/modules/pagination/pagination.min.css";
SwiperCore.use([Autoplay, Pagination]);
export default {
components: {
Swiper,
SwiperSlide,
Header,
},
setup() {
const swiper_options = new reactive({
pagination: {
clickable: true,
},
autoplay: {
disableOnInteraction: false, // 鼠标滑动后继续自动播放
delay: 2000, //2秒切换一次
pauseOnMouseEnter: true,
},
speed: 500, //切换过渡速度
});
//获取数据
const route = useRoute();
const router = useRouter();
const go_url = () => {
router.push("/cart_index");
};
const data = reactive({
shop: [],
});
GoodsDetails({ id: route.query.id }).then((e) => {
// console.log(e);
data.shop = e.data;
});
return {
swiper_options,
go_url,
title: "详情",
...toRefs(data),
};
},
};
</script>
<style scoped>
.swiper {
margin: 0 0.1rem;
}
.swiper .swiper-wrapper {
/* 图片容器必须要有高度,否则下面图片不能正常显示 */
height: 1.5rem;
border-radius: 0.1rem;
}
.swiper .swiper-wrapper img {
height: 100%;
width: 100%;
border-radius: 0.1rem;
}
.swiper {
--swiper-pagination-color: #fff;
--swiper-navigation-color: black;
}
.rele-wrap {
background: #f5f5f5;
}
.has-bottom {
padding-bottom: 55px !important;
}
.banner {
position: relative;
background: #fff;
overflow: hidden;
margin: 0;
visibility: visible;
height: 315px;
}
.banner .swipe-wrap {
overflow: hidden;
position: relative;
}
.vi_img {
float: left;
width: 100%;
height: 100%;
position: relative;
}
.idle-panel {
padding: 1px 12px 12px 12px;
background: #fff;
padding-left: 12px;
margin-top: 0;
}
.p1 {
font-weight: normal;
line-height: 22px;
padding-bottom: 0;
color: #222;
padding: 15px 10px 8px 0;
font-size: 100%;
position: relative;
}
.p2 {
height: 20px;
padding-top: 2px;
padding-right: 10px;
color: #222;
}
.p2 .span {
font-size: 16px;
line-height: 20px;
color: #ff3600;
float: right;
}
.sale-panel {
margin-top: 10px;
background: #fff;
padding: 1px 12px 12px 12px;
}
.tit {
letter-spacing: 1px;
color: #666;
height: 14px;
line-height: 14px;
font-size: 14px;
padding-left: 12px;
position: relative;
margin: 15px 0 0;
}
.tit::after {
position: absolute;
content: "";
width: 2px;
height: 100%;
background: #56b244;
left: 5px;
top: 0;
}
.open {
height: auto;
text-overflow: inherit;
-webkit-line-clamp: 100000;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 22px;
margin-top: 13px;
font-size: 14px;
color: #333;
position: relative;
}
.open img {
width: 100%;
}
/* 底部按钮 */
.fui-navbar {
position: fixed;
width: 100%;
bottom: 0;
z-index: 2;
}
.btn {
border: none;
font-size: 0.7rem;
color: #fff;
border-radius: 0;
width: 50%;
height: 49px;
}
.btn_left {
background: #fe9402;
float: left;
}
.btn_right {
float: right;
background: #fd5555;
}
</style>
参数传递需要注意中间件配置:tp\app\middleware.php
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
// \think\middleware\SessionInit::class
//允许跨域传值
\think\middleware\AllowCrossDomain::class,
];