搜索功能

07-01 搜索导航栏

一. 流程

复制代码
'''
# 拓展:  京东 360buy -> jd

# 全文检索
    问题: 数据量的庞大
    解决: 全文检索引擎(elasticsearch 一来七课社区). java封装的一个数据库, 专注于大数据的搜索
            
# 前端: 头部搜索组件 + 搜索页面
    1. 新建页面: SearchCourse.vue
    2. 配置路由: index.js 
        path: '/course/search'
        name: 'SearchCourse'
        component: SearchCourse
    3.     拷贝搜索页面
        提示:如果search没有写返回的就是所有的内容
    4. Header.vue组件 right-part下 添加form搜索组件, 记得拿form组件的css样式, 还有搜索相关的data数据,  以及methods中的功能
    5. 修改前端路由 search_action方法中 this.$router.push中的路由跳转路径地址
    6. 难点: 查询的单词'word=值'需要返回前端, 然后通过'search=值'发送asiox
    7. 重点: 
        this.$route.query  从?号后取值
        this.$route.params 从路径中取值
        提示: wd  word 二个关键字都可以搜索 (模仿百度的这种思路www.baidu.com/?wd=shuaige)
    8. h1标签中定义总共书写到了多少门课程
    总结: 二个点
        第一点: 
            路由: path: '/course/search?word=值'
            组件: this.$route.query  从?号后取值
        第二点: .params
            路由: path: '/course/:id'
            组件: this.$route.params  从路径中取值
'''

二. 路由器/ index.js

复制代码
import SearchCourse from '../views/SearchCourse.vue'

const routes = [
    // ...
    {
        path: '/course/search',
        name: 'SearchCourse',
        component: SearchCourse,
    },
];

三. 头部组件Header.vue

复制代码
<form class="search">
    <div class="tips" v-if="is_search_tip">
        <span @click="search_action('Python')">Python</span>
        <span @click="search_action('Linux')">Linux</span>
    </div>
    <input type="text" :placeholder="search_placeholder" @focus="on_search" @blur="off_search" v-model="search_word">
    <button type="button" class="glyphicon glyphicon-search" @click="search_action(search_word)"></button>
</form>

<script>
    export default {
        data() {
            return {
                is_search_tip: true,
                search_placeholder: '',
                search_word: ''
            }
        },
        methods: {
            search_action(search_word) {
                if (!search_word) {
                    this.$message('请输入要搜索的内容');
                    return
                }
                if (search_word !== this.$route.query.word) {
                    this.$router.push(`/course/search?word=${search_word}`);
                }
                this.search_word = '';
            },
            on_search() {
                this.search_placeholder = '请输入想搜索的课程';
                this.is_search_tip = false;
            },
            off_search() {
                this.search_placeholder = '';
                this.is_search_tip = true;
            },
        },
    }
</script>

<style scoped>
    .search {
        float: right;
        position: relative;
        margin-top: 22px;
        margin-right: 10px;
    }

    .search input, .search button {
        border: none;
        outline: none;
        background-color: white;
    }

    .search input {
        border-bottom: 1px solid #eeeeee;
    }

    .search input:focus {
        border-bottom-color: orange;
    }

    .search input:focus + button {
        color: orange;
    }

    .search .tips {
        position: absolute;
        bottom: 3px;
        left: 0;
    }

    .search .tips span {
        border-radius: 11px;
        background-color: #eee;
        line-height: 22px;
        display: inline-block;
        padding: 0 7px;
        margin-right: 3px;
        cursor: pointer;
        color: #aaa;
        font-size: 14px;

    }

    .search .tips span:hover {
        color: orange;
    }
</style>

07-02 搜索后台接口

一. 流程

复制代码
'''
# 后端    
    1. 模糊匹配: SearchFilter
    2. 自己写: 自定义过滤类, 继承filter_queryset, 指定'字段__contains', return queryset
    3. 搜索接口: 不和课程一起, 坨到一起了
        CourseSearchView 继承 G/L
        内部书写SearchFilter的即可
        提示: 需要携带分页
    4. 配置路由: search
'''

二. 路由:course / urls.py

复制代码
router.register('search', views.CourseSearchView, 'search')

三. 视图:course / views.py

复制代码
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from rest_framework.filters import SearchFilter
from . import models, serializers
from .filters import CourseFilter

# 搜索课程接口
class SearchCourseViewSet(ListModelMixin, GenericViewSet):
    queryset = Course.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = CourseModelSerializer

    # 提示: 需要携带分页
    pagination_class = PageNumberPagination

    # 自定义
    # filter_backends = [CourseFilter]

    # 使用SearchFilter
    filter_backends = [SearchFilter]
    search_fields = ['name']

07-03 搜索页面

一. 流程

复制代码
'''
# 前端: 头部搜索组件 + 搜索页面
    1. 新建页面: SearchCourse.vue
    2. 配置路由: index.js 
        path: '/search'
        name: 'SearchCourse'
        component: SearchCourse
    3. 拷贝搜索页面
        提示:如果search没有写返回的就是所有的内容
    4. Header.vue组件 right-part下 添加form搜索组件, 记得拿form组件的css样式, 还有搜索相关的data数据,  以及methods中的功能
    5. 修改前端路由 search_action方法中 this.$router.push中的路由跳转路径地址
    6. 难点: 查询的单词'word=值'需要返回前端, 然后通过'search=值'发送asiox
    7. 重点: 
        this.$route.query  从?号后取值
        this.$route.params 从路径中取值
        提示: wd  word 二个关键字都可以搜索 (模仿百度的这种思路www.baidu.com/?wd=shuaige)
    8. h1标签中定义总共书写到了多少门课程
    总结: 二个点
        第一点: 
            路由: path: '/course/search?word=值'
            组件: this.$route.query  从?号后取值
        第二点: .params
            路由: path: '/course/:id'
            组件: this.$route.params  从路径中取值
'''

二. SearchCourse搜索页面

复制代码
<template>
    <div class="search-course course">
        <Header/>

        <!-- 课程列表 -->
        <div class="main">
            <div v-if="course_list.length > 0" class="course-list">
                <div class="course-item" v-for="course in course_list" :key="course.name">
                    <div class="course-image">
                        <img :src="course.course_img" alt="">
                    </div>
                    <div class="course-info">
                        <h3>
                            <router-link :to="'/free/detail/'+course.id">{{course.name}}</router-link>
                            <span><img src="@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习</span></h3>
                        <p class="teather-info">
                            {{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}
                            <span v-if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时</span>
                            <span v-else>共{{course.sections}}课时/更新完成</span>
                        </p>
                        <ul class="section-list">
                            <li v-for="(section, key) in course.section_list" :key="section.name"><span
                                    class="section-title">0{{key+1}}  |  {{section.name}}</span>
                                <span class="free" v-if="section.free_trail">免费</span></li>
                        </ul>
                        <div class="pay-box">
                            <div v-if="course.discount_type">
                                <span class="discount-type">{{course.discount_type}}</span>
                                <span class="discount-price">¥{{course.real_price}}元</span>
                                <span class="original-price">原价:{{course.price}}元</span>
                            </div>
                            <span v-else class="discount-price">¥{{course.price}}元</span>
                            <span class="buy-now">立即购买</span>
                        </div>
                    </div>
                </div>
            </div>
            <div v-else style="text-align: center; line-height: 60px">
                没有搜索结果
            </div>
            <div class="course_pagination block">
                <el-pagination
                        @size-change="handleSizeChange"
                        @current-change="handleCurrentChange"
                        :current-page.sync="filter.page"
                        :page-sizes="[2, 3, 5, 10]"
                        :page-size="filter.page_size"
                        layout="sizes, prev, pager, next"
                        :total="course_total">
                </el-pagination>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from '../components/Header'

    export default {
        name: "SearchCourse",
        components: {
            Header,
        },
        data() {
            return {
                course_list: [],
                course_total: 0,
                filter: {
                    page_size: 10,
                    page: 1,
                    search: '',
                }
            }
        },
        created() {
            this.get_course()
        },
        watch: {
            '$route.query' () {
                this.get_course()
            }
        },
        methods: {
            handleSizeChange(val) {
                // 每页数据量发生变化时执行的方法
                this.filter.page = 1;
                this.filter.page_size = val;
            },
            handleCurrentChange(val) {
                // 页码发生变化时执行的方法
                this.filter.page = val;
            },
            get_course() {
                // 获取搜索的关键字
                this.filter.search = this.$route.query.word || this.$route.query.wd;

                // 获取课程列表信息
                this.$axios.get(`${this.$settings.base_url}/course/search/`, {
                    params: this.filter
                }).then(response => {
                    // 如果后台不分页,数据在response.data中;如果后台分页,数据在response.data.results中
                    this.course_list = response.data.results;
                    this.course_total = response.data.count;
                }).catch(() => {
                    this.$message({
                        message: "获取课程信息有误,请联系客服工作人员"
                    })
                })
            }
        }
    }
</script>

<style scoped>
    .course {
        background: #f6f6f6;
    }

    .course .main {
        width: 1100px;
        margin: 35px auto 0;
    }

    .course .condition {
        margin-bottom: 35px;
        padding: 25px 30px 25px 20px;
        background: #fff;
        border-radius: 4px;
        box-shadow: 0 2px 4px 0 #f0f0f0;
    }

    .course .cate-list {
        border-bottom: 1px solid #333;
        border-bottom-color: rgba(51, 51, 51, .05);
        padding-bottom: 18px;
        margin-bottom: 17px;
    }

    .course .cate-list::after {
        content: "";
        display: block;
        clear: both;
    }

    .course .cate-list li {
        float: left;
        font-size: 16px;
        padding: 6px 15px;
        line-height: 16px;
        margin-left: 14px;
        position: relative;
        transition: all .3s ease;
        cursor: pointer;
        color: #4a4a4a;
        border: 1px solid transparent; /* transparent 透明 */
    }

    .course .cate-list .title {
        color: #888;
        margin-left: 0;
        letter-spacing: .36px;
        padding: 0;
        line-height: 28px;
    }

    .course .cate-list .this {
        color: #ffc210;
        border: 1px solid #ffc210 !important;
        border-radius: 30px;
    }

    .course .ordering::after {
        content: "";
        display: block;
        clear: both;
    }

    .course .ordering ul {
        float: left;
    }

    .course .ordering ul::after {
        content: "";
        display: block;
        clear: both;
    }

    .course .ordering .condition-result {
        float: right;
        font-size: 14px;
        color: #9b9b9b;
        line-height: 28px;
    }

    .course .ordering ul li {
        float: left;
        padding: 6px 15px;
        line-height: 16px;
        margin-left: 14px;
        position: relative;
        transition: all .3s ease;
        cursor: pointer;
        color: #4a4a4a;
    }

    .course .ordering .title {
        font-size: 16px;
        color: #888;
        letter-spacing: .36px;
        margin-left: 0;
        padding: 0;
        line-height: 28px;
    }

    .course .ordering .this {
        color: #ffc210;
    }

    .course .ordering .price {
        position: relative;
    }

    .course .ordering .price::before,
    .course .ordering .price::after {
        cursor: pointer;
        content: "";
        display: block;
        width: 0px;
        height: 0px;
        border: 5px solid transparent;
        position: absolute;
        right: 0;
    }

    .course .ordering .price::before {
        border-bottom: 5px solid #aaa;
        margin-bottom: 2px;
        top: 2px;
    }

    .course .ordering .price::after {
        border-top: 5px solid #aaa;
        bottom: 2px;
    }

    .course .ordering .price_up::before {
        border-bottom-color: #ffc210;
    }

    .course .ordering .price_down::after {
        border-top-color: #ffc210;
    }

    .course .course-item:hover {
        box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
    }

    .course .course-item {
        width: 1100px;
        background: #fff;
        padding: 20px 30px 20px 20px;
        margin-bottom: 35px;
        border-radius: 2px;
        cursor: pointer;
        box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
        /* css3.0 过渡动画 hover 事件操作 */
        transition: all .2s ease;
    }

    .course .course-item::after {
        content: "";
        display: block;
        clear: both;
    }

    /* 顶级元素 父级元素  当前元素{} */
    .course .course-item .course-image {
        float: left;
        width: 423px;
        height: 210px;
        margin-right: 30px;
    }

    .course .course-item .course-image img {
        max-width: 100%;
        max-height: 210px;
    }

    .course .course-item .course-info {
        float: left;
        width: 596px;
    }

    .course-item .course-info h3 a {
        font-size: 26px;
        color: #333;
        font-weight: normal;
        margin-bottom: 8px;
    }

    .course-item .course-info h3 span {
        font-size: 14px;
        color: #9b9b9b;
        float: right;
        margin-top: 14px;
    }

    .course-item .course-info h3 span img {
        width: 11px;
        height: auto;
        margin-right: 7px;
    }

    .course-item .course-info .teather-info {
        font-size: 14px;
        color: #9b9b9b;
        margin-bottom: 14px;
        padding-bottom: 14px;
        border-bottom: 1px solid #333;
        border-bottom-color: rgba(51, 51, 51, .05);
    }

    .course-item .course-info .teather-info span {
        float: right;
    }

    .course-item .section-list::after {
        content: "";
        display: block;
        clear: both;
    }

    .course-item .section-list li {
        float: left;
        width: 44%;
        font-size: 14px;
        color: #666;
        padding-left: 22px;
        /* background: url("路径") 是否平铺 x轴位置 y轴位置 */
        background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
        margin-bottom: 15px;
    }

    .course-item .section-list li .section-title {
        /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */
        text-overflow: ellipsis;
        overflow: hidden;
        white-space: nowrap;
        display: inline-block;
        max-width: 200px;
    }

    .course-item .section-list li:hover {
        background-image: url("/src/assets/img/play-icon-yellow.svg");
        color: #ffc210;
    }

    .course-item .section-list li .free {
        width: 34px;
        height: 20px;
        color: #fd7b4d;
        vertical-align: super;
        margin-left: 10px;
        border: 1px solid #fd7b4d;
        border-radius: 2px;
        text-align: center;
        font-size: 13px;
        white-space: nowrap;
    }

    .course-item .section-list li:hover .free {
        color: #ffc210;
        border-color: #ffc210;
    }

    .course-item {
        position: relative;
    }

    .course-item .pay-box {
        position: absolute;
        bottom: 20px;
        width: 600px;
    }

    .course-item .pay-box::after {
        content: "";
        display: block;
        clear: both;
    }

    .course-item .pay-box .discount-type {
        padding: 6px 10px;
        font-size: 16px;
        color: #fff;
        text-align: center;
        margin-right: 8px;
        background: #fa6240;
        border: 1px solid #fa6240;
        border-radius: 10px 0 10px 0;
        float: left;
    }

    .course-item .pay-box .discount-price {
        font-size: 24px;
        color: #fa6240;
        float: left;
    }

    .course-item .pay-box .original-price {
        text-decoration: line-through;
        font-size: 14px;
        color: #9b9b9b;
        margin-left: 10px;
        float: left;
        margin-top: 10px;
    }

    .course-item .pay-box .buy-now {
        width: 120px;
        height: 38px;
        background: transparent;
        color: #fa6240;
        font-size: 16px;
        border: 1px solid #fd7b4d;
        border-radius: 3px;
        transition: all .2s ease-in-out;
        float: right;
        text-align: center;
        line-height: 38px;
        position: absolute;
        right: 0;
        bottom: 5px;
    }

    .course-item .pay-box .buy-now:hover {
        color: #fff;
        background: #ffc210;
        border: 1px solid #ffc210;
    }

    .course .course_pagination {
        margin-bottom: 60px;
        text-align: center;
    }
</style>
相关推荐
牢七21 小时前
5655869
django
秋氘渔2 天前
智演沙盘 —— 基于大模型的智能面试评估系统
python·mysql·django·drf
jcsx3 天前
如何将django项目发布为https
python·https·django
百锦再3 天前
京东云鼎入驻方案解读——通往协同的“高架桥”与“快速路”
android·java·python·rust·django·restful·京东云
Warren983 天前
datagrip新建oracle连接教程
数据库·windows·云原生·oracle·容器·kubernetes·django
韩立学长3 天前
【开题答辩实录分享】以《跳蚤市场二手物品交易推荐平台》为例进行选题答辩实录分享
python·django
飞天小蜈蚣3 天前
django的ulr注意事项、模板渲染
python·django·sqlite
Q_Q5110082853 天前
python_django基于大数据技术旅游景点数据分析推荐系统现_wrqk1aes
大数据·python·django
心本无晴.4 天前
拣学--基于vue3和django框架实现的辅助考研系统
vue.js·python·mysql·考研·django·dify
Darenm1114 天前
关于AI 面试官项目:智选ai 基于 Vue3 + Django + Dify 的全栈开发实战
人工智能·python·django