# 一、完成工程案例、资讯中心页面渲染

# 1. 控制器 app/controller/api/template01/api.js

const pageType = [
    'piclist', //图片列表展示
    'newslist', //新闻列表展示
    'mixlist', //图文混合列表展示
];
...
//其它页面
async ortherpage() {
    const { ctx, app } = this;

    //页面显示形式pageType
    let  _pageType = ctx.query.pageType;
    //js判断数组中是否包含某个元素
    if(!pageType.includes(_pageType)){
        _pageType = pageType[0];
    }
    //传参获取分类id
    let category_id = parseInt(ctx.params.category_id);
    const data =  await ctx.apiPage('News',{
        category_id:category_id
    });
    //由于多次用到分类相关信息
    let category = await ctx.model.Category.findByPk(category_id);
    console.log('分类信息category',JSON.parse(JSON.stringify(category)));
    await ctx.renderApi({
        tempType: 'ortherpage',
        data,
        pageType:_pageType,
        category,
    }, 'api/template01/main_app.html');
    
}

# 2. 模版 app/view/api/template01/main_app.html

{% if tempType == 'ortherpage' %}
    {% include "api/template01/_ortherpage.html" %}
{% endif %}

# 3. 模版 app/view/api/template01/_ortherpage.html

<!-- 内页广告图 -->
<div style="margin-top: 100px;">
    <img src="/public/api/template01/static/image/neibg.jpg" style="width: 100%;height: auto;display: block;">
</div>
<!-- 栏目名称 -->
<div class="column-name flex justify-center">
    <div class="column-name-div">
        <!-- 图标 -->
        <div class="icon-div">
            <span class="iconfont icon-zhuye"></span>
        </div>
        <!-- 栏目名 -->
        <span class="name">{{category.name}}</span>
    </div>
</div>

{{pageType}}

{% if pageType == 'piclist' %}
    {# 图片列表模式 #}
    <!-- 页面内容 -->
    <div class="page-content flex justify-center">
        <div style="width: 1200px;" class="py-40 case-div">
            <!-- 内容 -->
            {% for item in data %}
            <div class="list">
                <!-- 图片 -->
                <div>
                    <img src="{{item.attachment}}" title="{{item.name}}">
                </div>
                <!-- 标题 -->
                <div class="text-center mt-2">
                    <a href="./case_detail.html" target="_blank" class="text-no-underline text-dark lines-1 display-block"
                        title="{{item.name}}">{{item.name}}</a>
                </div>
            </div>    
            {% endfor %}
            <!-- 分页 -->
            <div style="width: 100%;">
                {# 安全起见,egg.js官方默认将代码过滤成字符串,这里我们采用过滤器进行解码 #}
                {{ ctx.locals.apipageHtml | safe }}
            </div>
        </div>
    </div>   
{% endif %}
{% if pageType == 'newslist' %}
    {# 新闻列表展示 #}
    <!-- 页面内容 -->
    <div class="page-content flex justify-center">
        <div style="width: 1200px;" class="py-40">
            <!-- 名称 -->
            <div class="flex align-center">
                <h4 class="mr-2 font-lger text-dark text-center pb-2"
                style="flex:auto;">{{category.name}}</h4>
            </div>
            <!-- 内容 -->
            {% if data.length > 0 %}
            <ul class="news-ul">
                {% for item in data %}
                <li>
                    <a href="./news_detail.html">
                        <span class="mr-1">+</span>
                        <span>{{item.name}}</span>
                        <span class="time">{{item.create_time.split(' ')[0]}}</span>
                    </a> 
                </li>    
                {% endfor %}
            </ul>    
            {% else %}
            <div class="flex align-center justify-center"> <span>当前栏目管理员还没有上传数据!</span></span></div>    
            {% endif %}
            <!-- 分页 -->
            <div style="width: 100%;">
                {# 安全起见,egg.js官方默认将代码过滤成字符串,这里我们采用过滤器进行解码 #}
                {{ ctx.locals.apipageHtml | safe }}
            </div>
            
        </div>
    </div>
{% endif %}

# 4. 扩展 app/extend/context.js

// 前端分页 -- 模版1分页样式
async apiPage(modelName, where = {}, options = {}) {
    let modeldata = await this.apiModelData(modelName, where, options);
    console.log('modeldata的数据',modeldata);
    ...
    //如果当前就是第一页,就不存在上一页,禁用上一页按钮,下一页同理
    let prevDisabled = (page <= 1) || (totalPage == 0) ? 'disabled' : '';
    let nextDisabled = (page >= totalPage) || (totalPage == 0) ? 'disabled' : '';
    let firstpage = (page == 1) || (totalPage == 0) ? 'disabled' : '';
    let lastpage = (page == totalPage) || (totalPage == 0) ? 'disabled' : '';
    ...
}

# 5. 路由 app/router/api/template01/router.js

router.get('/api/template/01/cid/:category_id', controller.api.template01.api.ortherpage);

# 二、文章详情页数据渲染

# 1. 控制器 app/controller/api/template01/api.js

//数据详情页
async pageinfo(){
    const { ctx, app } = this;

    //页面显示形式pageType
    let _pageType = ctx.query.pageType;
    //js判断数组中是否包含某个元素
    if(!pageType.includes(_pageType)){
        _pageType = 'detail';
    }

    let id = parseInt(ctx.params.id);
    let news = await ctx.model.News.findOne({
        where:{
            id,
            status:1
        }
    });
    console.log('新闻内容',JSON.parse(JSON.stringify(news)));
    let category = await ctx.model.Category.findByPk(news.category_id);

    //上一篇下一篇
    const Op = this.app.Sequelize.Op;//拿Op,固定写法
    const prevdata = await ctx.model.News.findOne({
        where:{
            id:{
                [Op.lt]:id
            },
            category_id:news.category_id,
            status:1
        },
        order:[['id','DESC']]
    });
    const nextdata = await ctx.model.News.findOne({
        where:{
            id:{
                [Op.gt]:id
            },
            category_id:news.category_id,
            status:1
        },
        order:[['id','ASC']]
    });

    await ctx.renderApi({
        tempType: 'ortherpage',
        news,
        pageType:_pageType,
        category,
        ortherdata:{
            prevdata,
            nextdata
        }
    }, 'api/template01/main_app.html'); 

}

# 2. 路由 app/router/api/template01/router.js

router.get('/api/template/01/id/:id', controller.api.template01.api.pageinfo);

# 3. 模版 app/view/api/template01/_ortherpage.html

{# 信息详情 #}
{% if pageType == 'detail' %}
 <!-- 页面内容 -->
 <div class="page-content flex justify-center">
    <div style="width: 1200px;" class="py-40">
        <!-- 名称 -->
        <div class="flex justify-between border-bottom"
        style="align-items: baseline;">
            <h4 class="mr-2 font-lger text-dark">{{news.name}}</h4>
            <span class="font text-light-muted" style="width: 100px;">{{news.create_time.split(' ')[0]}}</span>
        </div>
        <!-- 内容 -->
        <div>
            {{news.maincontent | safe}}
        </div>
        <!-- 上一篇下一篇 -->
        <ul class="prev-next">
            {% if ortherdata and ortherdata.prevdata %}
            <li>
                <a href="#">
                    <span style="margin-right: 15px;color:#999999;">上一篇</span>
                    <span>{{ortherdata.prevdata.name}}</span>
                    <span>{{ortherdata.prevdata.create_time.split(' ')[0]}}</span>
                </a>
            </li>
            {% endif %}
            {% if ortherdata and ortherdata.nextdata %}
            <li>
                <a href="#">
                    <span style="margin-right: 15px;color:#999999;">下一篇</span>
                    <span>{{ortherdata.nextdata.name}}</span>
                    <span>{{ortherdata.nextdata.create_time.split(' ')[0]}}</span>
                </a>
            </li>
            {% endif %}
        </ul>
        <!-- 返回按钮 -->
        <div class="flex align-center justify-end my-5">
            <a href="#"
            class="py-3 px-5 text-white bg-info bg-hover-info">返回列表</a>
        </div>
        
    </div>
</div>    
{% endif %}

# 三、工程案例、资讯中心等文章链接完善,返回列表功能完善

# 1. 控制器 app/controller/api/template01/api.js

...
//其它页面
async ortherpage() {
    记得先修改扩展里:面前端分页 -- 模版1分页样式--apiPage函数返回值
    return modeldata.data;
    ...
    //如果分类里面只有一条数据,则直接转到新闻详情
    if(data.count == 1){
        let id = data.rows[0].id;
        ctx.redirect(`/api/template/01/id/${id}?pageType=detail`);
        return;
    }
    ...
    await ctx.renderApi({
        ...
        data:data.rows,
        ...
    }, 'api/template01/main_app.html');
}

...

//数据详情页
async pageinfo() {
    ...
    //看一下该分类下有几篇文章,如果只有一篇,则不显示返回列表按钮
    let categorynews = await ctx.model.News.findAndCountAll({
        where:{
            category_id:news.category_id,
            status:1
        },
        // 主表查询字段限制
        attributes:['id','name'],
    });
    console.log('该分类下有几篇文章',JSON.parse(JSON.stringify(categorynews)));
    ...
    await ctx.renderApi({
        ...
        ortherdata:{
            ...
            categorynewsCount:categorynews.count,//分类下几篇文章
            frompageType:ctx.query.frompageType //返回分类列表的分类页面类型
        }
    }, 'api/template01/main_app.html');
}
...

# 2. 模版 app/view/api/template01/_ortherpage.html

每个栏目的链接改一下
...
<!-- 标题 -->
<div class="text-center mt-2">
    <a href="/api/template/01/id/{{item.id}}?pageType=detail&frompageType={{pageType}}"  class="text-no-underline text-dark lines-1 display-block"
        title="{{item.name}}">{{item.name}}</a>
</div>
...


<!-- 上一篇下一篇 -->
<li>
    <a href="/api/template/01/id/{{ortherdata.prevdata.id}}?pageType=detail">
        <span style="margin-right: 15px;color: #999999;">上一篇</span>
        <span>{{ortherdata.prevdata.name}}</span>
        <span>{{ortherdata.prevdata.create_time.split(' ')[0]}}</span>
    </a>
</li>  
<li>
    <a href="/api/template/01/id/{{ortherdata.nextdata.id}}?pageType=detail">
        <span style="margin-right: 15px;color: #999999;">下一篇</span>
        <span>{{ortherdata.nextdata.name}}</span>
        <span>{{ortherdata.nextdata.create_time.split(' ')[0]}}</span>
    </a>
</li>

返回列表
<!-- 返回按钮 -->
{% if ortherdata.categorynewsCount > 1 %}
<div class="flex align-center justify-end my-5">
    <a href="/api/template/01/cid/{{category.id}}?pageType={{ortherdata.frompageType}}"
    class="py-3 px-5 text-white bg-info bg-hover-info">返回列表</a>
</div>   
{% endif %}

# 四、处理网站导航栏及选中情况

# ① 获取导航栏数据,完成选中:工程案例、资讯中心

  1. 数据库分类表category新增字段(isnav是否是导航栏上的分类)、(categoryurl 链接地址)
    category表 迁移文件 和 模型:
isnav: {
    type: INTEGER(1),
    allowNull: false,
    defaultValue: 0,
    comment: '该分类是否也是导航栏的栏目 0否 1是'
},
categoryurl: {
    type: STRING,//不限定长度.默认varchar(255)
    allowNull: true,
    defaultValue: '',
    comment: '分类链接地址'
},

记得数据库category表也要修改一下!

  1. 完成后台分类管理新增、修改界面和提交数据新增的两个字段,分类列表也要增加这两个字段展示 app/controller/admin/category.js
//分类列表页面 
async index() {
    //表头
    columns: [
        ...
        {
            title: '是否是导航栏栏目',
            key: 'isnav',
            width: 200,//可选
            class: 'text-center',//可选
            hidekeyData: true,//是否隐藏key对应的数据
            render(item) {
                console.log('可用状态里面每个item', item);
                let arr = [
                    { value: 1, name: '是' },
                    { value: 0, name: '否' },
                ];
                let str = `<div class="btn-group btn-group-${item.id}">`;
                for (let i = 0; i < arr.length; i++) {
                    str += `<button type="button" class="btn btn-light" data="${item.isnav}"
                    value="${arr[i].value}"
                    @click="changeBtnStatus('isnav','btn-group-${item.id}',${arr[i].value},${i},${item.id},'category','Category')">${arr[i].name}</button>`;
                }
                str += `</div>`;
                return str;
            }
        },
        {
            title: '分类链接地址',
            key: 'categoryurl',
            class: 'text-center',//可选
        },
        ...
    ]
}

//创建分类表单  async create() 
//修改分类界面  async edit()
//  字段
fields: [
    ...
    {
        label: '分类链接地址',
        type: 'text',
        name: 'categoryurl',
        placeholder: '请输入分类链接地址,选填',
    },
    ...
]


//分类表单提交数据 async save()
//修改分类提交数据 async update()
//1.参数验证
categoryurl: {
    type: 'string',
    required: false,
    defValue: '',
    desc: '分类链接地址'
},
...
const { ...,categoryurl } = ctx.request.body;
...
category.categoryurl = categoryurl; 修改分类提交数据 async update()
...
categoryurl 分类表单提交数据 async save()
  1. 控制器 读取导航栏数据分发到模版页面 app/controller/api/template01/api.js
//导航栏数据
async nav(){
    const { ctx, app } = this;
    let nav = await ctx.model.Category.findAll({
        where:{
            isnav:1,
            status:1
        },
        order:[
            ['order','asc']
        ]
    });
    return nav;
}

增加到各个方法里面
nav: await this.nav(), //导航栏数据
  1. 导航栏模版 app/view/api/template01/_header.html
<!-- 导航栏右边部分  导航内容部分 -->
<div class="right-div flex align-center ">
    {# <a href="/api/template/01"  class="active">网站首页</a>
    <a href="/api/template/01/cid/5?pageType=newslist" >关于我们</a>
    <a href="/api/template/01/cid/6?pageType=newslist" >资讯中心</a>
    <a href="/api/template/01/cid/7?pageType=newslist" >主营业务</a>
    <a href="/api/template/01/cid/8?pageType=piclist&limit=16" >工程案例</a>
    <a href="/api/template/01/cid/9?pageType=newslist" >联系我们</a> #}
    {# 可采用在控制器判断变量来设置哪个栏目选中,也可以用vue.js来处理,也可以用jquery来处理选中问题 #}
    {% for item in nav %}
    <a href="{{item.categoryurl if item.categoryurl else '/api/template/01/cid/' ~ item.id ~ '?pageType=newslist'}}"  
    dataid="{{item.id}}">{{item.name}}</a>
    {% endfor %}
</div>
...
<script>
    $(function(){
        let url = location.href;
        let cid = url.substr(0,url.indexOf('?'));
        cid = cid.substring(cid.lastIndexOf('/') + 1);
        console.log(cid);
        $('#nav').find('a').each(function(index,element){
            if($(element).attr('dataid') == cid){
                $(element).addClass('active');
            }
        });
    });
</script>

记得主模板引入jquery app/view/api/template01/main_app.html

<script src="/public/api/template01/static/js/jquery.1.11.3.min.js"></script>

关于jquery我们在上一个季度详细讲过了,jquery.js文件可以去下载上一个季度课程里面的文件, 也可以 https://www.bootcdn.cn/jquery/ (opens new window)

# ② 完成导航栏其他栏目的选中问题

  1. 路由 app/router/api/template01/router.js
//数据详情页
router.get('/api/template/01/id/:id/cid/:category_id', controller.api.template01.api.pageinfo);
  1. 模版 app/view/api/template01/_ortherpage.html
...
<a href="/api/template/01/id/{{item.id}}/cid/{{category.id}}?pageType=detail&frompageType={{pageType}}"
...
<!-- 上一篇下一篇 -->
<a href="/api/template/01/id/{{ortherdata.prevdata.id}}/cid/{{category.id}}?pageType=detail&frompageType={{ortherdata.frompageType}}">
<a href="/api/template/01/id/{{ortherdata.nextdata.id}}/cid/{{category.id}}?pageType=detail&frompageType={{ortherdata.frompageType}}">
...
  1. 控制器 app/controller/api/template01/api.js 分类只有一条数据的跳转
//其它页面
async ortherpage() {
  ...
    //如果分类里面只有一条数据,则直接转到新闻详情
    if (data.count == 1) {
        let id = data.rows[0].id;
        ctx.redirect(`/api/template/01/id/${id}/cid/${category_id}?pageType=detail`);
        return;
    }
  ...
}

# 五、网站首页导航栏选中、及网站配置展示

  1. 路由 app/router/api/template01/router.js
module.exports = app => {
    const { router, controller } = app;

    //首页或前缀
    const main_index = app.config.globalVar.template01.index;
    //首页 '/api/template/01'
    router.get(main_index, controller.api.template01.api.index);
    //其它页面一般是分类页面
    router.get(main_index + '/cid/:category_id', controller.api.template01.api.ortherpage);
    //数据详情页
    router.get(main_index + '/id/:id/cid/:category_id', controller.api.template01.api.pageinfo);
}
  1. 全局 config/config.default.js
  //配置全局变量
  config.globalVar = {
     template01:{
       index:'/api/template/01', //首页或前缀
     }
  };
  1. 控制器 app/controller/api/template01/api.js 读取网站配置json文件和全局变量
//获取配置数据
async getsiteConfigJson() {
    //先判断是否存在这个json文件或者文件夹
    if (!fs.existsSync(paths.file)) {
        return '';
    } else {
        let data = fs.readFileSync(paths.file, {
            encoding: 'utf-8'
        });
        console.log(JSON.parse(data));
        return JSON.parse(data)
    }
}

await ctx.renderApi({
    ...
    siteconfig:await this.getsiteConfigJson(), //网站配置
    main_index: app.config.globalVar.template01.index, //首页或前缀
    ...
});
  1. 模版 app/view/api/template01/_header.html
<!-- 导航栏 -->
<div class="flex justify-center" id="nav"
    index="{{main_index}}"
    style="position: fixed;left: 0px;right: 0px;top: 0px;z-index: 1;background-color: #ffffff;">
    <div style="width: 1200px;" class="flex justify-between  align-center">
        <!-- 导航栏的左边 logo图 -->
        <div class="left-logo flex align-center">
            <img src="/public/api/template01/static/image/logo.png" />
        </div>
        <!-- 导航栏右边部分  导航内容部分 -->
        <div class="right-div flex align-center ">
            {# <a href="/api/template/01" class="active">网站首页</a>
            <a href="/api/template/01/cid/5?pageType=newslist">关于我们</a>
            <a href="/api/template/01/cid/6?pageType=newslist">资讯中心</a>
            <a href="/api/template/01/cid/7?pageType=newslist">主营业务</a>
            <a href="/api/template/01/cid/8?pageType=piclist&limit=16">工程案例</a>
            <a href="/api/template/01/cid/9?pageType=newslist">联系我们</a> #}
            {% for item in nav %}
            <a
                href="{{item.categoryurl if item.categoryurl else  main_index ~'/cid/'~item.id~'?pageType=newslist'}}"
                dataid="{{item.id}}">
                {{item.name}}</a>
            {% endfor %}
        </div>
    </div>
</div>

<script>
    $(function(){
        let url = location.href;
        let origin = location.origin;
        let cid = url.substr(0,url.indexOf('?'));
        cid = cid.substring(cid.lastIndexOf('/') + 1);
        // console.log(cid);
        $('#nav').find('a').each(function(index,element){
           if($(element).attr('dataid') == cid){
              $(element).addClass('active');
           }
           //首页选中
           //console.log($('#nav').attr('index'));
           //console.log(url.replace(origin,''));
           //console.log($('#nav').attr('index'));
           if($('#nav').attr('index') && $('#nav').attr('index') == url.replace(origin,'') && $(element).attr('href') == $('#nav').attr('index')){
              $(element).addClass('active');
           }
        });
    });

</script>
  1. 模版 app/view/api/template01/_ortherpage.html
    替换 /api/template/01{ {main_index} }

  2. 模版 app/view/api/template01/_bottom.html

<!-- 底部 -->
<div id="page_footer" class="flex justify-center py-5">
    <div style="width: 1200px;" class="flex justify-between">
        <!-- 左边 -->
        <div class="flex align-center">
            <!-- logo -->
            <img src="/public/api/template01/static/image/footer_logo.png" style="width: 107px;height: 107px;">
            <!-- 地址信息 -->
            <div class="ml-2 text-light-muted">
                <p>{{siteconfig.data[0].corporate_name}}</p>
                <p>地址:{{siteconfig.data[0].address}}</p>
                <p>
                    {{siteconfig.data[0].copyright}} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{{siteconfig.data[0].icpNumber}}
                </p>
            </div>
        </div>
        <!-- 右边 -->
        <div class="flex align-center">
            <!-- 电话图标 -->
            <img src="/public/api/template01/static/image/footer_tel.png" style="width: 56px;height: 56px;">
            <!-- 电话 -->
            <div class="ml-2 text-light-light-muted">
                <p>业务咨询电话</p>
                <strong class="font-max">{{siteconfig.data[0].tel}}</strong>
            </div>
        </div>
    </div>
</div>

# 六、关于我们、联系我们页面单独处理

  1. 细节处理:看本节课的课程视频
  2. 引入不同的界面到 app/view/api/template01/_ortherpage.html
{% if ortherType == 'aboutus' %}
{% include "api/template01/_aboutus.html" %}  
{% endif %}
{% if ortherType == 'contactus' %}
{% include "api/template01/_contactus.html" %}  
{% endif %}
  1. 控制器 app/controller/api/template01/api.js 在路由上新增字段进行判断,如:
    http://127.0.0.1:7001/api/template/01/id/1/cid/5?pageType=detail&ortherType=aboutus
    http://127.0.0.1:7001/api/template/01/cid/9?ortherType=contactus
ortherType: ctx.query.ortherType, //其他模块如联系我们关于我们

# 七、网站留言板提交留言到后台

  1. 用上个季度的留言板代码 替换 当前留言板代码
  2. 引入相应的js文件
  3. 写接口 app/controller/api/template01/api.js
  4. 调试留言板

    具体看本节课视频。
更新时间: 2024年6月3日星期一中午11点47分