提交 0c8a2ef4 编写于 作者: 黛琳ghz's avatar 黛琳ghz

上传的新的项目

上级 7fe48a3d
> 1%
last 2 versions
not dead
not ie 11
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/standard'
],
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: false
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# 1024程序员开源挑战赛
**任务快速入口**
- 任务一,赢取 一年10核10G云实验环境会员:[GitCode平台基础操作](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E5%BC%80%E6%BA%90%E4%BB%BB%E5%8A%A11.md)
- 2022/10/26 [37人开发者任务完成名单](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E4%BB%BB%E5%8A%A11%E4%B8%AD%E5%A5%96%E5%90%8D%E5%8D%95.md#1-20221026-%E4%BB%BB%E5%8A%A1%E5%AE%8C%E6%88%90%E5%90%8D%E5%8D%95%E5%85%AC%E5%B8%8337%E4%BA%BA)
- 2022/10/25 [48人开发者任务完成名单](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E4%BB%BB%E5%8A%A11%E4%B8%AD%E5%A5%96%E5%90%8D%E5%8D%95.md#2-20221025-%E4%BB%BB%E5%8A%A1%E5%AE%8C%E6%88%90%E5%90%8D%E5%8D%95%E5%85%AC%E5%B8%8348%E4%BA%BA)
- 2022/10/24 [58人开发者任务完成名单](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E4%BB%BB%E5%8A%A11%E4%B8%AD%E5%A5%96%E5%90%8D%E5%8D%95.md#3-20221024-%E4%BB%BB%E5%8A%A1%E5%AE%8C%E6%88%90%E5%90%8D%E5%8D%95%E5%85%AC%E5%B8%83%E5%85%B1%E8%AE%A158%E4%BA%BA)
- 2022/10/23 [22人开发者任务完成名单](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E4%BB%BB%E5%8A%A11%E4%B8%AD%E5%A5%96%E5%90%8D%E5%8D%95.md#4-20221023-%E4%BB%BB%E5%8A%A1%E5%AE%8C%E6%88%90%E5%90%8D%E5%8D%95%E5%85%AC%E5%B8%83-%E5%85%B1%E8%AE%A122%E4%BA%BA)
- 任务二,赢取 CSDN定制书包:[开源项目开发与运行](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E5%BC%80%E6%BA%90%E4%BB%BB%E5%8A%A12.md)
- 任务三,赢取 现金大礼:[1024云IDE应用挑战赛](https://gitcode.net/cloud-ide/1024)
上述每项任务无顺序依赖关系,三项任务都可参与或独立参与。
## 一、活动介绍
**1024开源挑战赛** 是CSDN官方在1024程序员节重磅推出的开源活动,致力让开发者使用和参与开源项目。在“开源正在吞噬世界”的大背景下,94%的开发者正在使用开源项目与软件,此次活动希望广大开发者了解开源的同时获取社区奖励。
## 二、活动安排
| 序号 | 任务名称 | 用户群体 | 时间安排 |
| ---- | ---- | ---- | ---- |
| 1 | GitCode平台基础操作 | 开源使用者 | 2022.10.23 - 2022.11.14 |
| 2 | 开源项目开发与运行 | 开源使用者 | 2022.10.23 - 2022.11.14 |
| 3 | 1024云IDE应用挑战赛 | 开源贡献者 | 2022.10.23 - 2022.11.14 |
上述每项任务无顺序依赖关系,三项任务都可参与或独立参与。
## 三、奖品设置及发放规则
| 序号 | 任务名称 | 任务奖励 | 领取条件 | 发放规则 | 人数限制 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 1 | GitCode平台基础操作 | [一年10核10G云实验环境会员](https://mydev.csdn.net/product/pod/new)(非云服务器,是云容器实验环境,详细介绍[戳我查看](https://mydev.csdn.net/product/pod/new)) | 完成任务即可发放,用户可按照任务操作步骤自行检查是否完成,官方会每天运行程序,自动统计并发放奖励。每个用户仅可领取1次。| 次日12点前在任务页面公布中奖名单,24点前完成发放奖励,成功发放后会有短信通知,若未收到短信,可点击[此链接,](https://mydev.csdn.net/product/ide/dashboard )查看VIP是否到账(成功领取到的用户,可在页面看到VIP标识及到期时间等提示) |100,000 |
| 2 | 开源项目开发与运行 | [CSDN定制书包](https://img-home.csdnimg.cn/images/20221013053300.png) | 完成任务即可发放,用户可按照任务操作步骤自行检查是否完成,官方人员会每周统一通过检查后发放。每个用户仅可领取1次。 | 每周一12点前在任务页面公布中奖名单,工作人员会通过CSDN私信联系您,奖品在活动结束后30个工作日发放 | 2,000 |
| 3 | 1024云IDE应用挑战赛 | 最高5000元现金奖励 | 代码提交完成,评选获奖后发放 | 11月15日在任务页面公布评选结果,工作人员会通过CSDN私信联系您,奖品在活动结束后7个工作日发放 | 88 |
## 四、参与任务入口
| 序号 | 任务名称 | 任务入口 |
| ---- | ---- | ---- |
| 1 | GitCode平台基础操作| [https://gitcode.net/gitcode/1024opensource/-/blob/master/%E5%BC%80%E6%BA%90%E4%BB%BB%E5%8A%A11.md](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E5%BC%80%E6%BA%90%E4%BB%BB%E5%8A%A11.md) |
| 2 | 开源项目开发与运行 | [https://gitcode.net/gitcode/1024opensource/-/blob/master/%E5%BC%80%E6%BA%90%E4%BB%BB%E5%8A%A12.md](https://gitcode.net/gitcode/1024opensource/-/blob/master/%E5%BC%80%E6%BA%90%E4%BB%BB%E5%8A%A12.md) |
| 3 | [1024云IDE应用挑战赛](https://gitcode.net/cloud-ide/1024) | [https://gitcode.net/cloud-ide/1024](https://gitcode.net/cloud-ide/1024) |
## 五、活动交流
开源活动官方交流群(仅参与者之间讨论和交流)。扫码进入选手沟通群。活动重要节点通知会在群内第一时间告知,请所有参与者尽量加群。
<img src="https://gitcode.net/gitcode/1024opensource/uploads/c94527ea69ee6d19fd3a65550cbd321b/image.png" width="400px" style="border: 6px solid #fff;border-radius: 2px;">
## 六、活动点评
### 活动建议
如果大家对本次活动有建议,可以直接在本项目中提交issue,我们会认真对待每一个评论,非常感谢大家!!!
### 活动点赞👍
如果大家喜欢本次开源挑战赛活动,可以点击本项目的star,我们会根据大家的star数作为后期是否长期举办的重要依据,非常感谢大家!!!
## 七、活动声明
本活动最终解释权归CSDN所有,所有中奖名单会在活动结束后进行公示
# rabbit_ghw
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
module.exports = {
'*.{js,jsx,vue}': 'vue-cli-service lint'
}
此差异已折叠。
{
"name": "rabbit_ghw",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@vueuse/core": "^4.9.0",
"axios": "^0.27.2",
"core-js": "^3.8.3",
"normalize.css": "^8.0.1",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vuex": "^4.0.0",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^8.0.3",
"less": "^4.0.0",
"less-loader": "^8.0.0",
"lint-staged": "^11.1.2"
},
"gitHooks": {
"pre-commit": "lint-staged"
}
}
# preview.yml
autoOpen: false # 打开工作空间时是否自动开启所有应用的预览
apps:
- port: 5500 # 应用的端口
run: npm i --registry=https://registry.npm.taobao.org && npm run serve # 应用的启动命
command: # 使用此命令启动服务,且不执行run
root: ./ # 应用的启动目录
name: 2048 # 应用名称
description: 我的第一个 App。 # 应用描述
autoOpen: true # 打开工作空间时是否自动开启预览(优先级高于根级 autoOpen
<!DOCTYPE html>
<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.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- <link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css"> -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
rabbit_ghw @ d88b8941
Subproject commit d88b89417035115c6859502b2a80218163563e8e
<template>
<!-- 一级路由-->
<!-- 模板根目录只需要一个元素 vue2.0 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'App'
}
</script>
// 定义首页需要的接口函数
import request from '@/utils/request'
/**
* 获取首页头部分类数据
*/
export const findAllCategory = () => {
return request('/home/category/head', 'get')
}
// 顶级分类
export const topCategory = [
'居家',
'美食',
'服饰',
'母婴',
'个护',
'严选',
'数码',
'运动',
'杂货'
]
import request from '../utils/request'
/**
* 获取品牌推荐
* @returns Promise
*/
export const findBrand = (limit = 6) => {
return request('/home/brand', 'get', { limit })
}
/**
* 获取广告图
* @returns Promise
*/
export const findBanner = () => {
return request('/home/banner', 'get')
}
/**
* 新鲜好物
* @returns Promise
*/
export const findNew = () => {
return request('home/new', 'get')
}
/**
* 人气推荐
* @returns Promise
*/
export const findHot = () => {
return request('home/hot', 'get')
}
/**
* 获取商品板块
* @returns {Promise}
*/
export const findGoods = () => {
return request('home/goods', 'get')
}
*{
margin: 0;
padding: 0;
}
\ No newline at end of file
// 重置样式
* {
box-sizing: border-box;
}
html {
height: 100%;
font-size: 14px;
}
body {
height: 100%;
color: #333;
min-width: 1240px;
font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif
}
ul,
h1,
h3,
h4,
p,
dl,
dd {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
color: #333;
outline: none;
}
i {
font-style: normal;
}
input[type="text"],
input[type="search"],
input[type="password"],
input[type="checkbox"] {
padding: 0;
outline: none;
border: none;
-webkit-appearance: none;
&::placeholder {
color: #ccc;
}
}
img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
}
ul {
list-style: none;
}
#app {
background: #f5f5f5;
user-select: none;
}
.container {
width: 1240px;
margin: 0 auto;
position: relative;
}
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.ellipsis-2 {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.fl {
float: left;
}
.fr {
float: right;
}
.clearfix:after {
content: ".";
display: block;
visibility: hidden;
height: 0;
line-height: 0;
clear: both;
}
.fade {
&-leave {
&-active {
position: absolute;
width: 100%;
transition: opacity .5s .2s;
z-index: 1;
}
&-to {
opacity: 0;
}
}
}
@font-face {font-family: "iconfont";
src: url('//at.alicdn.com/t/font_2143783_iq6z4ey5vu.eot?t=1612407837378'); /* IE9 */
src: url('//at.alicdn.com/t/font_2143783_iq6z4ey5vu.eot?t=1612407837378#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('//at.alicdn.com/t/font_2143783_iq6z4ey5vu.woff?t=1612407837378') format('woff'),
url('//at.alicdn.com/t/font_2143783_iq6z4ey5vu.ttf?t=1612407837378') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('//at.alicdn.com/t/font_2143783_iq6z4ey5vu.svg?t=1612407837378#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-backtop:before {
content: "\e622";
}
.icon-comment-filling:before {
content: "\e689";
}
.icon-dynamic-filling:before {
content: "\e68a";
}
.icon-favorite-filling:before {
content: "\e68b";
}
.icon-task-filling:before {
content: "\e68c";
}
.icon-close-new:before {
content: "\e668";
}
.icon-bind:before {
content: "\e63f";
}
.icon-edit:before {
content: "\e615";
}
.icon-msg:before {
content: "\e62d";
}
.icon-fenxiang:before {
content: "\e60f";
}
.icon-duigou:before {
content: "\e605";
}
.icon-order-comment:before {
content: "\e60a";
}
.icon-angle-down:before {
content: "\e7b5";
}
.icon-order-deliver:before {
content: "\e64a";
}
.icon-shanchu:before {
content: "\e613";
}
.icon-see:before {
content: "\e6ee";
}
.icon-chakan2:before {
content: "\e6ef";
}
.icon-queren:before {
content: "\e6f8";
}
.icon-queren2:before {
content: "\e6f9";
}
.icon-hart1:before {
content: "\e711";
}
.icon-hart2:before {
content: "\e712";
}
.icon-message:before {
content: "\e7b4";
}
.icon-code:before {
content: "\e656";
}
.icon-position:before {
content: "\e61e";
}
.icon-weixin:before {
content: "\e62e";
}
.icon-order-receive:before {
content: "\e6b1";
}
.icon-weibo:before {
content: "\e63e";
}
.icon-angle-right:before {
content: "\e612";
}
.icon-angle-left:before {
content: "\e614";
}
.icon-unchecked:before {
content: "\e626";
}
.icon-checked:before {
content: "\e627";
}
.icon-cart:before {
content: "\e60c";
}
.icon-lock:before {
content: "\e6e9";
}
.icon-tip:before {
content: "\e628";
}
.icon-order-complete:before {
content: "\e666";
}
.icon-warning:before {
content: "\e763";
}
.icon-guanbi:before {
content: "\e608";
}
.icon-hy:before {
content: "\e74a";
}
.icon-dw:before {
content: "\e751";
}
.icon-aq:before {
content: "\e753";
}
.icon-phone:before {
content: "\e633";
}
.icon-dianzan:before {
content: "\e719";
}
.icon-search:before {
content: "\e6c7";
}
.icon-question:before {
content: "\e675";
}
.icon-wjx02:before {
content: "\e629";
}
.icon-wjx01:before {
content: "\e62b";
}
.icon-order-unpay:before {
content: "\e635";
}
.icon-kefu:before {
content: "\e625";
}
.icon-safe:before {
content: "\e60d";
}
.icon-down-time:before {
content: "\e609";
}
.icon-footer01:before {
content: "\e602";
}
.icon-footer03:before {
content: "\e600";
}
.icon-footer02:before {
content: "\e601";
}
.icon-xiaoxi:before {
content: "\e61a";
}
.icon-user:before {
content: "\e655";
}
.icon-order-cancel:before {
content: "\e61b";
}
\ No newline at end of file
// 鼠标经过上移阴影动画
.hoverShadow () {
transition: all .5s;
&:hover {
transform: translate3d(0, -3px, 0);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
}
}
// 主题
@xtxColor: #27BA9B;
// 辅助
@helpColor: #E26237;
// 成功
@sucColor: #1DC779;
// 警告
@warnColor: #FFB302;
// 价格
@priceColor: #CF4444;
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
<template>
<footer class="app-footer">
<!-- 联系我们 -->
<div class="contact">
<div class="container">
<dl>
<dt>客户服务</dt>
<dd><i class="iconfont icon-kefu"></i> 在线客服</dd>
<dd><i class="iconfont icon-question"></i> 问题反馈</dd>
</dl>
<dl>
<dt>关注我们</dt>
<dd><i class="iconfont icon-weixin"></i> 公众号</dd>
<dd><i class="iconfont icon-weibo"></i> 微博</dd>
</dl>
<dl>
<dt>一站式服务系统(广告)</dt>
<dd class="qrcode"><img src="../assets/images/一站式服务系统.png"/></dd>
<dd class="download">
<span>扫描二维码</span>
<span>了解一站式服务系统</span>
<a href="http://139.9.40.2/html/home.html" target="_trant">点击了解</a>
</dd>
</dl>
<dl>
<dt>服务热线</dt>
<dd class="hotline">400-0000-000 <small>周一至周日 8:00-18:00</small></dd>
</dl>
</div>
</div>
<!-- 其它 -->
<div class="extra">
<div class="container">
<div class="slogan">
<a href="javascript:;">
<i class="iconfont icon-footer01"></i>
<span>价格亲民</span>
</a>
<a href="javascript:;">
<i class="iconfont icon-footer02"></i>
<span>物流快捷</span>
</a>
<a href="javascript:;">
<i class="iconfont icon-footer03"></i>
<span>品质新鲜</span>
</a>
</div>
<!-- 版权信息 -->
<div class="copyright">
<p>
<a href="javascript:;">关于我们</a>
<a href="javascript:;">帮助中心</a>
<a href="javascript:;">售后服务</a>
<a href="javascript:;">配送与验收</a>
<a href="javascript:;">商务合作</a>
<a href="javascript:;">搜索推荐</a>
<a href="javascript:;">友情链接</a>
</p>
<p>CopyRight © 小兔鲜儿</p>
</div>
</div>
</div>
</footer>
</template>
<script>
export default {
name: 'AppFooter'
}
</script>
<style scoped lang='less'>
.app-footer {
overflow: hidden;
background-color: #f5f5f5;
padding-top: 20px;
.contact {
background: #fff;
.container {
padding: 60px 0 40px 25px;
display: flex;
}
dl {
height: 190px;
text-align: center;
padding: 0 72px;
border-right: 1px solid #f2f2f2;
color: #999;
&:first-child {
padding-left: 0;
}
&:last-child {
border-right: none;
padding-right: 0;
}
}
dt {
line-height: 1;
font-size: 18px;
}
dd {
margin: 36px 12px 0 0;
float: left;
width: 92px;
height: 92px;
padding-top: 10px;
border: 1px solid #ededed;
.iconfont {
font-size: 36px;
display: block;
color: #666;
}
&:hover {
.iconfont {
color:#27BA9B;
}
}
&:last-child {
margin-right: 0;
}
}
.qrcode {
width: 92px;
height: 92px;
padding: 7px;
border: 1px solid #ededed;
}
.download {
padding-top: 5px;
font-size: 14px;
width: auto;
height: auto;
border: none;
span {
display: block;
}
a {
display: block;
line-height: 1;
padding: 10px 25px;
margin-top: 5px;
color: #fff;
border-radius: 2px;
background-color:#27BA9B;
}
}
.hotline {
padding-top: 20px;
font-size: 22px;
color: #666;
width: auto;
height: auto;
border: none;
small {
display: block;
font-size: 15px;
color: #999;
}
}
}
.extra {
background-color: #333;
}
.slogan {
height: 178px;
line-height: 58px;
padding: 60px 100px;
border-bottom: 1px solid #434343;
display: flex;
justify-content: space-between;
a {
height: 58px;
line-height: 58px;
color: #fff;
font-size: 28px;
i {
font-size: 50px;
vertical-align: middle;
margin-right: 10px;
font-weight: 100;
}
span {
vertical-align: middle;
text-shadow: 0 0 1px #333;
}
}
}
.copyright {
height: 170px;
padding-top: 40px;
text-align: center;
color: #999;
font-size: 15px;
p {
line-height: 1;
margin-bottom: 20px;
}
a {
color: #999;
line-height: 1;
padding: 0 10px;
border-right: 1px solid #999;
&:last-child {
border-right: none;
}
}
}
}
</style>
<template>
<ul class="app-header-nav">
<li class="home">
<RouterLink to="/">首页</RouterLink>
</li>
<li v-for="item in list" :key="item.id" @mouseenter="show(item)" @mouseleave="hide(item)">
<RouterLink :to="`/category/${item.id}`" @click="hide(item)">{{item.name}}</RouterLink>
<div class="layer" :class="{open:item.open}">
<ul>
<li v-for="sub in item.children" :key="sub.id">
<RouterLink :to="`/category/sub/${sub.id}`" @click="hide(item)">
<img :src="sub.picture" alt="">
<p>{{sub.name}}</p>
</RouterLink>
</li>
</ul>
</div>
</li>
</ul>
</template>
<script>
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
name: 'AppHeaderNav',
setup () {
const store = useStore()
const list = computed(() => {
return store.state.category.list
})
const show = (item) => {
store.commit('category/show', item)
}
const hide = (item) => {
store.commit('category/hide', item)
}
return {
list,
show,
hide
}
}
}
</script>
<style scoped lang='less'>
.app-header-nav {
width: 820px;
display: flex;
justify-content: space-around;
padding-left: 40px;
position: relative;
z-index: 998;
> li {
margin-right: 40px;
width: 38px;
text-align: center;
> a {
font-size: 16px;
line-height: 32px;
height: 32px;
display: inline-block;
}
&:hover {
> a {
color: #27BA9B;
border-bottom: 1px solid #27BA9B;
}
}
}
}
.layer {
width: 1240px;
background-color: #fff;
position: absolute;
left: -200px;
top: 56px;
height: 0;
overflow: hidden;
opacity: 0;
box-shadow: 0 0 5px #ccc;
transition: all .2s .1s;
&.open {
height: 132px;
opacity: 1;
}
ul {
display: flex;
flex-wrap: wrap;
padding: 0 70px;
align-items: center;
height: 132px;
li {
width: 110px;
text-align: center;
img {
width: 60px;
height: 60px;
}
p {
padding-top: 10px;
}
&:hover {
p {
color:#27BA9B;
}
}
}
}
}
</style>
<template>
<div class="app-header-sticky" :class="{show:y>=78}">
<div class="container" v-show="y>=78">
<RouterLink class="logo" to="/"/>
<AppHeaderNav/>
<div class="right">
<RouterLink to="/">品牌</RouterLink>
<RouterLink to="/">专题</RouterLink>
</div>
</div>
</div>
</template>
<script>
import AppHeaderNav from './app-header-nav'
import { onMounted, ref } from 'vue'
export default {
name: 'AppHeaderSticky',
components: { AppHeaderNav },
setup () {
// 记录y轴卷曲高度
const y = ref(0)
onMounted(() => {
window.onscroll = () => {
const scrollTop = document.documentElement.scrollTop
y.value = scrollTop
}
})
return { y }
}
}
</script>
<style scoped lang='less'>
.app-header-sticky {
width: 100%;
height: 80px;
position: fixed;
left: 0;
top: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1px solid #e4e4e4;
transform: translateY(-100%);
opacity: 0;
&.show {
transition: all 0.3s linear;
transform: none;
opacity: 1;
}
.container {
display: flex;
align-items: center;
}
.logo {
width: 200px;
height: 80px;
background: url(../assets/images/logo.png) no-repeat right 2px;
background-size: 160px auto;
}
.right {
width: 220px;
display: flex;
text-align: center;
padding-left: 40px;
border-left: 2px solid #27BA9B;
a {
width: 38px;
margin-right: 40px;
font-size: 16px;
line-height: 1;
&:hover {
color: #27BA9B;
}
}
}
}
</style>
<template>
<header class='app-header'>
<div class="container">
<h1 class="logo">
<RouterLink to="/">小兔鲜</RouterLink>
</h1>
<AppHeaderNav/>
<div class="search">
<i class="iconfont icon-search"></i>
<input type="text" placeholder="搜一搜">
</div>
<div class="cart">
<a class="curr" href="#">
<i class="iconfont icon-cart"></i><em>993</em>
</a>
</div>
</div>
</header>
</template>
<script>
import AppHeaderNav from '@/components/app-header-nav'
export default {
name: 'AppHeader',
components: {
AppHeaderNav
}
}
</script>
<style scoped lang='less'>
.app-header {
background: #fff;
.container {
display: flex;
align-items: center;
}
.logo {
width: 200px;
a {
display: block;
height: 132px;
width: 100%;
text-indent: -9999px;
background: url(../assets/images/logo.png) no-repeat center 18px / contain;
}
}
.navs {
width: 820px;
display: flex;
justify-content: space-around;
padding-left: 40px;
li {
margin-right: 40px;
width: 38px;
text-align: center;
a {
font-size: 16px;
line-height: 32px;
height: 32px;
display: inline-block;
}
&:hover {
a {
color: #27BA9B;
border-bottom: 1px solid #27BA9B;
}
}
}
}
.search {
width: 170px;
height: 32px;
position: relative;
border-bottom: 1px solid #e7e7e7;
line-height: 32px;
.icon-search {
font-size: 18px;
margin-left: 5px;
}
input {
width: 140px;
padding-left: 5px;
color: #666;
}
}
.cart {
width: 50px;
.curr {
height: 32px;
line-height: 32px;
text-align: center;
position: relative;
display: block;
.icon-cart{
font-size: 22px;
}
em {
font-style: normal;
position: absolute;
right: 0;
top: 0;
padding: 1px 6px;
line-height: 1;
background: #E26237;
color: #fff;
font-size: 12px;
border-radius: 10px;
font-family: Arial;
}
}
}
}
</style>
<template>
<nav class="app-topnav">
<div class="container">
<ul>
<template v-if="profile.token">
<li><a href="javascript:;"><i class="iconfont icon-user"></i>{{profile.account}}</a></li>
<li><a href="javascript:;">退出登录</a></li>
</template>
<template v-else>
<li><a href="javascript:;">请先登录</a></li>
<li><a href="javascript:;">免费注册</a></li>
</template>
<li><a href="javascript:;">我的订单</a></li>
<li><a href="javascript:;">会员中心</a></li>
<li><a href="javascript:;">帮助中心</a></li>
<li><a href="javascript:;">关于我们</a></li>
<li><a href="javascript:;"><i class="iconfont icon-phone"></i>手机版</a></li>
</ul>
</div>
</nav>
</template>
<script>
import { useStore } from 'vuex'
import { computed } from 'vue'
// import store from '@/store'
export default {
name: 'AppTopnav',
setup () {
// 获取用户信息的登录信息才能控制切换导航栏菜单
// 根据用户登录信息控制切换导航菜单
const store = useStore()
// 使用vuex中的state需要使用计算属性
const profile = computed(() => {
return store.state.user.profile
})
// const { profile } = store.state.profile
return { profile }
}
}
</script>
<style scoped lang="less">
body{
margin: 0;
padding: 0;
}
.app-topnav {
background: #333;
ul {
display: flex;
height: 53px;
justify-content: flex-end;
align-items: center;
li {
a {
padding: 0 15px;
color: #cdcdcd;
line-height: 1;
display: inline-block;
i {
font-size: 14px;
margin-right: 2px;
}
&:hover {
color:#27BA9B;
}
}
// ~ 作用是选择当前选择后所有元素
~ li {
a {
border-left: 2px solid #666;
}
}
}
}
}
</style>
// 扩展vue原有的功能:全局组件,自定义指令,挂载原型方法,注意:没有全局过滤器。
// 这就是插件
// vue2.0插件写法要素:导出一个对象,有install函数,默认传入了Vue构造函数,Vue基础之上扩展
// vue3.0插件写法要素:导出一个对象,有install函数,默认传入了app应用实例,app基础之上扩展
import XtxSkeleton from '../library/xtx-skeleton.vue'
import XtxCarousel from '../library/xtx-carousel.vue'
import XtxMore from '../library/xtx-more'
import defaultImg from '@/assets/images/200.png'
// 指令
const defineDirective = (app) => {
// 图片懒加载指令
app.directive('lazyload', {
mounted (el, binding) {
const observer = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) {
observer.unobserve(el)
el.onerror = () => {
el.src = defaultImg
}
el.src = binding.value
}
}, {
threshold: 0.01
})
observer.observe(el)
}
})
}
export default {
install (app) {
// 在app上进行扩展,app提供 component directive 函数
// 如果要挂载原型 app.config.globalProperties 方式
app.component(XtxSkeleton.name, XtxSkeleton)
app.component(XtxCarousel.name, XtxCarousel)
app.component(XtxMore.name, XtxMore)
defineDirective(app)
}
}
<template>
<div class='xtx-carousel' @mouseenter="stop()" @mouseleave="start()">
<!-- 图片容器-->
<ul class="carousel-body">
<!-- fade是显示-->
<li class="carousel-item" v-for="(item,i) in sliders" :key="i" :class="{fade:index===i}">
<RouterLink to="/">
<img :src="item.imgUrl" alt="">
</RouterLink>
</li>
</ul>
<!-- 上一张-->
<a @click="toggle(-1)" href="javascript:" class="carousel-btn prev"><i class="iconfont icon-angle-left"></i></a>
<!-- 下一张-->
<a @click="toggle(1)" href="javascript:" class="carousel-btn next"><i class="iconfont icon-angle-right"></i></a>
<!-- 指示器-->
<div class="carousel-indicator">
<span @click="index=i" v-for="(item,i) in sliders" :key="i" :class="{active:index===i}"></span>
</div>
</div>
</template>
<script>
import { onUnmounted, ref, watch } from 'vue'
export default {
name: 'XtxCarousel',
props: {
// 轮播图数据
sliders: {
type: Array,
default: () => []
},
duration: {
type: Number,
default: 3000
},
autoPlay: {
type: Boolean,
default: false
}
},
setup (props) {
// 默认显示的图片的索引
const index = ref(0)
// 自动播放
let timer = null
const autoPlayFn = () => {
// 清除已有定时器,防止重复
clearInterval(timer)
timer = setInterval(() => {
index.value++
if (index.value >= props.sliders.length) {
index.value = 0
}
}, props.duration)
}
watch(() => props.sliders, (newVal) => {
// 有数据&开启自动播放,才调用自动播放函数
if (newVal.length && props.autoPlay) {
index.value = 0
autoPlayFn()
}
}, { immediate: true })
// 鼠标进入停止,移出开启自动,前提条件:autoPlay为true
const stop = () => {
if (timer) clearInterval(timer)
}
const start = () => {
if (props.sliders.length && props.autoPlay) {
autoPlayFn()
}
}
// 上一张下一张
const toggle = (step) => {
const newIndex = index.value + step
if (newIndex >= props.sliders.length) {
index.value = 0
return
}
if (newIndex < 0) {
index.value = props.sliders.length - 1
return
}
index.value = newIndex
}
// 组件消耗,清理定时器
onUnmounted(() => {
clearInterval(timer)
})
return {
index,
stop,
start,
toggle
}
}
}
</script>
<style scoped lang="less">
.xtx-carousel {
width: 100%;
height: 100%;
min-width: 300px;
min-height: 150px;
position: relative;
.carousel {
&-body {
width: 100%;
height: 100%;
}
&-item {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0;
transition: opacity 0.5s linear;
&.fade {
opacity: 1;
z-index: 1;
}
img {
width: 100%;
height: 100%;
}
}
&-indicator {
position: absolute;
left: 0;
bottom: 20px;
z-index: 2;
width: 100%;
text-align: center;
span {
display: inline-block;
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
cursor: pointer;
~ span {
margin-left: 12px;
}
&.active {
background: #fff;
}
}
}
&-btn {
width: 44px;
height: 44px;
background: rgba(0, 0, 0, .2);
color: #fff;
border-radius: 50%;
position: absolute;
top: 228px;
z-index: 2;
text-align: center;
line-height: 44px;
opacity: 0;
transition: all 0.5s;
&.prev {
left: 20px;
}
&.next {
right: 20px;
}
}
}
&:hover {
.carousel-btn {
opacity: 1;
}
}
}
</style>
<template>
<RouterLink :to="path" class="xtx-more">
<span>查看全部</span>
<i class="iconfont icon-angle-right"></i>
</RouterLink>
</template>
<script>
export default {
name: 'XtxMore',
props: {
path: {
type: String,
default: '/'
}
}
}
</script>
<style scoped lang='less'>
@import '@/assets/styles/mixins.less';
@import '@/assets/styles/variables.less';
.xtx-more {
margin-bottom: 2px;
span {
font-size: 16px;
vertical-align: middle;
margin-right: 4px;
color: #999;
}
i {
font-size: 14px;
vertical-align: middle;
position: relative;
top: 2px;
color: #ccc;
}
&:hover {
span,
i {
color: @xtxColor;
}
}
}
</style>
<template>
<div class="xtx-skeleton" :style="{width,height}" :class="{shan:animated}">
<!-- 1 盒子-->
<div class="block" :style="{backgroundColor:bg}"></div>
<!-- 2 闪效果 xtx-skeleton 伪元素 --->
</div>
</template>
<script>
export default {
name: 'XtxSkeleton',
// 使用的时候需要动态设置 高度,宽度,背景颜色,是否闪下
props: {
bg: {
type: String,
default: '#efefef'
},
width: {
type: String,
default: '100px'
},
height: {
type: String,
default: '100px'
},
animated: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped lang="less">
.xtx-skeleton {
display: inline-block;
position: relative;
overflow: hidden;
vertical-align: middle;
.block {
width: 100%;
height: 100%;
border-radius: 2px;
}
}
.shan {
&::after {
content: "";
position: absolute;
animation: shan 1.5s ease 0s infinite;
top: 0;
width: 50%;
height: 100%;
background: linear-gradient(to left,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0) 100%);
transform: skewX(-45deg);
}
}
@keyframes shan {
0% {
left: -100%;
}
100% {
left: 120%;
}
}
</style>
// 提供复用逻辑的函数
// hooks 封装逻辑,提供响应式数据。
import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
// 数据懒加载函数
export const useLazyData = (apiFn) => {
// 需要
// 1. 被观察的对象
// 2. 不同的API函数
const target = ref(null)
const result = ref([])
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
if (isIntersecting) {
stop()
// 调用API获取数据
apiFn().then(data => {
result.value = data.result
})
}
}, {
threshold: 0
}
)
// 返回--->数据(dom,后台数据)
return {
target,
result
}
}
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'
import '@/assets/styles/common.less'
// 添加一个样式消除body的间隔
import '@/assets/styles/clear.less'
// 引入字体图标的样式
import '@/assets/styles/font.less'
import '@/assets/styles/variables.less'
import '@/assets/styles/mixins.less'
import ui from './components/library'
createApp(App).use(store).use(router).use(ui).mount('#app')
import { createRouter, createWebHashHistory } from 'vue-router'
const Layout = () => import('@/views/Layout')
const Home = () => import('@/views/home')
const TopCategory = () => import('@/views/category/index')
const SubCategory = () => import('@/views/category/sub')
// 路由规则
const routes = [
// 一级路由布局容器
{
path: '/',
component: Layout,
children: [
{ path: '/', component: Home },
{ path: '/category/:id', component: TopCategory },
{ path: '/category/sub/:id', component: SubCategory }
]
}
]
// vue2.0通过 new VueRouter({})创建路由实例
// vue3.0 createRouter({})
const router = createRouter({
// 使用hash的路由模式
history: createWebHashHistory(),
routes
})
export default router
// import { createStore } from 'vuex'
// vuex的五个属性是:
// 1、state属性,用来存储变量
// 2、getters属性,相当于state的计算属性
// 3、mutations属性,用于提交更新数据
// 4、actions属性,包含任意异步操作
// 5、modules属性,用于模块化vuex
import { createStore } from 'vuex'
import category from '@/store/modules/category'
import cart from '@/store/modules/cart'
import user from '@/store/modules/user'
import createPersistedState from 'vuex-persistedstate'
export default createStore({
modules: {
user,
cart,
category
},
plugins: [createPersistedState({
// 本地存储名字,默认存储在localstorage
key: 'erabbit-client-pc-store',
// 指定存储模块
paths: ['user', 'cart']
})]
})
// 购物车模块
export default {
namespaced: true,
state () {
return {
// 购物车商品列表
list: []
}
}
}
// 存储的分类数据
import { topCategory } from '@/api/constants'
import { findAllCategory } from '@/api/category'
export default {
namespaced: true,
state: () => {
return {
// 如果默认是[]数组,看不见默认的9个分类,等你数据加载完毕才会看到。
// 所以:根据常量数据来生成一个默认的顶级分类数据,不会出现空白(没数据的情况)
list: topCategory.map(item => ({ name: item }))
}
},
// 加载数据成功后需要修改list所以需要mutations函数
mutations: {
setList (state, payload) {
state.list = payload
},
// 修改当前一级分类下的open数据为true
show (state, item) {
const category = state.list.find(category => category.id === item.id)
category.open = true
},
// 修改当前一级分类下的open数据为false
hide (state, item) {
const category = state.list.find(category => category.id === item.id)
category.open = false
}
},
// 需要向后台加载数据,所以需要actions函数获取数据
actions: {
async getList ({ commit }) {
const data = await findAllCategory()
// 给一级分类加上一个控制二级分类显示隐藏的数据open
data.result.forEach(item => {
item.open = false
})
// 获取数据成功,提交mutations进行数据修改
commit('setList', data.result)
}
}
}
// 用户模块
export default {
namespaced: true,
state () {
return {
profile: {
id: '',
avatar: '',
nickname: '',
account: '',
mobile: '',
token: ''
}
}
},
mutations: {
// payload就是用户信息对象
setUser (state, payload) {
this.profile = payload
}
}
}
// 1. 创建一个新的axios实例
// 2. 请求拦截器,如果有token进行头部携带
// 3. 响应拦截器:1. 剥离无效数据 2. 处理token失效
// 4. 导出一个函数,调用当前的axsio实例发请求,返回值promise
import axios from 'axios'
import store from '@/store'
import router from '@/router'
// 导出基准地址,原因:其他地方不是通过axios发请求的地方用上基准地址
export const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net/'
// 1.创建一个新的axios实例
const instance = axios.create({
// axios 的一些配置,baseURL timeout
baseURL,
timeout: 5000
})
// 2.请求拦截器,如果有token进行头部携带
instance.interceptors.request.use(config => {
// 拦截业务逻辑
// 进行请求配置的修改
// 如果本地又token就在头部携带
// 1. 获取用户信息对象
const { profile } = store.state.user
// 统一添加请求头token(token存在时添加)
// 2. 判断是否有token
if (profile.token) {
// 3. 设置token
config.headers.Authorization = `Bearer ${profile.token}`
}
return config
}, err => {
// 请求失败
return Promise.reject(err)
})
// 3. 响应拦截器:1. 剥离无效数据 2. 处理token失效
// res => res.data 取出data数据,将来调用接口的时候直接拿到的就是后台的数据
instance.interceptors.response.use(res => res.data, err => {
// 401 状态码,进入该函数
if (err.response && err.response.status === 401) {
// 1. 清空无效用户信息
// 2. 跳转到登录页
// 3. 跳转需要传参(当前路由地址)给登录页
store.commit('user/setUser', {})
// 当前路由地址 fullPath
// 组件里头:`/user?a=10` $route.path === /user $route.fullPath === /user?a=10
// js模块中:router.currentRoute.value.fullPath 就是当前路由地址,router.currentRoute 是ref响应式数据
const fullPath = encodeURIComponent(router.currentRoute.value.fullPath)
// encodeURIComponent 转换uri编码,防止解析含有特殊字符的地址出问题
router.push('/login?redirectUrl=' + fullPath)
}
return Promise.reject(err)
})
// 请求工具函数
export default (url, method, submitData) => {
// 负责发请求:请求地址,请求方式,提交的数据
return instance({
url,
method,
// 1. 如果是get请求 需要使用params来传递submitData ?a=10&c=10
// 2. 如果不是get请求 需要使用data来传递submitData 请求体传参
// [] 设置一个动态的key, []内写js表达式,js表达式的执行结果当作KEY
// method参数:get,Get,GET 转换成小写再来判断
// 在对象,['params']:submitData ===== params:submitData 这样理解
[method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
})
}
<!-- ERROR Error: Cannot find module ‘vue-template-compiler/package.json‘ -->
<!-- 创建的app安装了缺少的模块 -->
<!-- npm install vue-template-compiler -->
<template>
<title>小兔鲜儿-新鲜 惠民 快捷!</title>
<!--顶部通栏-->
<app-topnav/>
<!-- 吸顶头部-->
<AppHeaderSticky/>
<!-- 头部组件-->
<AppHeader/>
<!-- 内容容器-->
<main class="app-body">
<!-- 二级路由-->
<router-view></router-view>
</main>
<!-- 底部组件-->
<AppFooter/>
</template>
<script>
// 有头有底的容器
import AppTopnav from '@/components/app-topnav'
import AppHeader from '@/components/app-header'
import AppFooter from '@/components/app-footer'
import AppHeaderSticky from '../components/app-header-sticky'
import { useStore } from 'vuex'
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Layout',
components: {
AppTopnav,
AppHeader,
AppFooter,
AppHeaderSticky
},
setup () {
const store = useStore()
store.dispatch('category/getList')
}
}
</script>
<style scoped>
.app-body {
min-height: 600px;
}
body{
margin: 0;
padding: 0;
}
</style>
<template>
<div>Top-Category</div>
</template>
<script>
export default {
name: 'TopCategory'
}
</script>
<style scoped lang='less'>
</style>
<template>
<div>Sub-Category</div>
</template>
<script>
export default {
name: 'SubCategory'
}
</script>
<style scoped lang="less"></style>
<template>
<div class="home-banner">
<XtxCarousel auto-play :sliders="sliders"/>
</div>
</template>
<script>
import { ref } from 'vue'
import { findBanner } from '@/api/home'
export default {
name: 'HomeBanner',
setup () {
const sliders = ref([])
findBanner().then(data => {
sliders.value = data.result
})
return { sliders }
}
}
</script>
<style scoped lang="less">
.home-banner {
width: 1240px;
height: 500px;
position: absolute;
left: 0;
top: 0;
z-index: 98
}
//覆盖样式
.xtx-carousel {
::v-deep .carousel-btn.prev {
left: 270px;
}
::v-deep .carousel-indicator {
padding-left: 250px;
}
}
</style>
<template>
<HomePanel title="热门品牌" sub-title="国际经典 品质保证">
<template v-slot:right>
<a @click="toggle(-1)" :class="{disabled:index===0}" href="javascript:" class="iconfont icon-angle-left prev"></a>
<a @click="toggle(1)" :class="{disabled:index===1}" href="javascript:" class="iconfont icon-angle-right next"></a>
</template>
<div ref="target" class="box">
<Transition name="fade">
<ul v-if="brands.length" class="list" :style="{transform:`translateX(${-index*1240}px)`}">
<li v-for="item in brands" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
</RouterLink>
</li>
</ul>
<div v-else class="skeleton">
<XtxSkeleton class="item" v-for="i in 5" :key="i" animated bg="#e4e4e4" width="240px" height="305px"/>
</div>
</Transition>
</div>
</HomePanel>
</template>
<script>
import { ref } from 'vue'
import HomePanel from './home-panel'
import { findBrand } from '@/api/home'
import { useLazyData } from '@/hooks'
import XtxSkeleton from '@/components/library/xtx-skeleton.vue'
export default {
name: 'HomeBrand',
components: {
HomePanel,
XtxSkeleton
},
setup () {
// 获取数据
const brands = ref([])
findBrand(10).then(data => {
brands.value = data.result
})
// 注意:useLazyData需要的是API函数,如果遇到要传参的情况,自己写函数再函数中调用API
const {
target,
result
} = useLazyData(() => findBrand(10))
// 切换效果,前提只有 0 1 两页
const index = ref(0)
// 1. 点击上一页
// 2. 点击下一页
const toggle = (step) => {
const newIndex = index.value + step
if (newIndex < 0 || newIndex > 1) return
index.value = newIndex
}
return {
brands: result,
toggle,
index,
target
}
}
}
</script>
<style scoped lang='less'>
.home-panel {
background: #f5f5f5
}
.iconfont {
width: 20px;
height: 20px;
background: #ccc;
color: #fff;
display: inline-block;
text-align: center;
margin-left: 5px;
background: #27BA9B;
&::before {
font-size: 12px;
position: relative;
top: -2px
}
&.disabled {
background: #ccc;
cursor: not-allowed;
}
}
.box {
display: flex;
width: 100%;
height: 345px;
overflow: hidden;
padding-bottom: 40px;
.list {
width: 200%;
display: flex;
transition: all 1s;
li {
margin-right: 10px;
width: 240px;
&:nth-child(5n) {
margin-right: 0;
}
img {
width: 240px;
height: 305px;
}
}
}
.skeleton {
width: 100%;
display: flex;
.item {
margin-right: 10px;
&:nth-child(5n) {
margin-right: 0;
}
}
}
}
</style>
<template>
<div class='home-category' @mouseleave="categoryId=null">
<ul class="menu">
<li :class="{active:categoryId===item.id}" v-for="item in menuList" :key="item.id"
@mouseenter="categoryId=item.id">
<RouterLink :to="`/category/${item.id}`">{{ item.name }}</RouterLink>
<template v-if="item.children">
<RouterLink
v-for="sub in item.children"
:key="sub.id"
:to="`/category/sub/${sub.id}`">
{{ sub.name }}
</RouterLink>
</template>
<template v-else>
<XtxSkeleton width="60px" height="18px" style="margin-right:5px" bg="rgba(255,255,255,0.2)"/>
<XtxSkeleton width="50px" height="18px" bg="rgba(255,255,255,0.2)"/>
</template>
</li>
</ul>
<!-- 弹层 -->
<div class="layer">
<h4>{{ currCategory && currCategory.id === 'brand' ? '品牌' : '推荐' }} <small>根据您的购买或浏览记录推荐</small></h4>
<ul v-if="currCategory && currCategory.goods && currCategory.goods.length">
<li v-for="item in currCategory.goods" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
<div class="info">
<p class="name ellipsis-2">{{ item.name }}</p>
<p class="desc ellipsis">{{ item.desc }}</p>
<p class="price"><i>¥</i>{{ item.price }}</p>
</div>
</RouterLink>
</li>
</ul>
<!-- 品牌-->
<ul v-if="currCategory&&currCategory.brands">
<li class="brand" v-for="brand in currCategory.brands" :key="brand.id">
<RouterLink to="/">
<img :src="brand.picture" alt="">
<div class="info">
<p class="place"><i class="iconfont icon-dingwei"></i>{{ brand.place }}</p>
<p class="name ellipsis">{{ brand.name }}</p>
<p class="desc ellipsis-2">{{ brand.desc }}</p>
</div>
</RouterLink>
</li>
</ul>
</div>
</div>
</template>
<script>
import { computed, reactive, ref } from 'vue'
import { useStore } from 'vuex'
import { findBrand } from '@/api/home'
export default {
name: 'HomeCategory',
// 组织所有数据逻辑
// 1. 获取vuex的一级分类,并且只需要两个二级分类
// 2. 需要在组件内部,定义一个品牌数据
// 3. 根据vuex的分类数据和组件中定义品牌数据,得到左侧分类完整数据(9分类+1品牌)数组
// 4. 进行渲染即可
setup () {
const brand = reactive({
id: 'brand',
name: '品牌',
children: [{
id: 'brand-children',
name: '品牌推荐'
}],
brands: []
})
const store = useStore()
const menuList = computed(() => {
// 取出vuex store的分类
const list = store.state.category.list.map(item => {
return {
id: item.id,
name: item.name,
// 防止初始化没有children的时候调用slice函数报错
children: item.children && item.children.slice(0, 2),
goods: item.goods
}
})
// 添加没有的分类
list.push(brand)
return list
})
// 获取当前分类逻辑
const categoryId = ref(null)
const currCategory = computed(() => {
return menuList.value.find(item => item.id === categoryId.value)
})
findBrand().then(data => {
brand.brands = data.result
})
return {
menuList,
categoryId,
currCategory
}
}
}
</script>
<style scoped lang='less'>
.home-category {
width: 250px;
height: 500px;
background: rgba(0, 0, 0, 0.8);
position: relative;
z-index: 99;
.menu {
li {
padding-left: 40px;
height: 50px;
line-height: 50px;
&:hover, &.active {
background: #27BA9B;
}
a {
margin-right: 4px;
color: #fff;
&:first-child {
font-size: 16px;
}
}
}
}
.layer {
width: 990px;
height: 500px;
background: rgba(255, 255, 255, 0.8);
position: absolute;
left: 250px;
top: 0;
display: none;
padding: 0 15px;
h4 {
font-size: 20px;
font-weight: normal;
line-height: 80px;
small {
font-size: 16px;
color: #666;
}
}
ul {
display: flex;
flex-wrap: wrap;
li {
width: 310px;
height: 120px;
margin-right: 15px;
margin-bottom: 15px;
border: 1px solid #eee;
border-radius: 4px;
background: #fff;
&:nth-child(3n) {
margin-right: 0;
}
a {
display: flex;
width: 100%;
height: 100%;
align-items: center;
padding: 10px;
&:hover {
background: #e3f9f4;
}
img {
width: 95px;
height: 95px;
}
.info {
padding-left: 10px;
line-height: 24px;
width: 190px;
.name {
font-size: 16px;
color: #666;
}
.desc {
color: #999;
}
.price {
font-size: 22px;
color:#CF4444;
i {
font-size: 16px;
}
}
}
}
}
li.brand {
height: 180px;
a {
align-items: flex-start;
img {
width: 120px;
height: 160px;
}
.info {
p {
margin-top: 8px;
}
.place {
color: #999;
}
}
}
}
}
}
&:hover {
.layer {
display: block;
}
}
}
// 骨架动画
.xtx-skeleton {
animation: fade 1s linear infinite alternate;
}
@keyframes fade {
from {
opacity: 0.2;
}
to {
opacity: 1;
}
}
</style>
<template>
<div class="goods-item">
<RouterLink :to="`/product/${goods.id}`" class="image">
<img :src="goods.picture" alt="" />
</RouterLink>
<p class="name ellipsis-2">{{goods.name}}</p>
<p class="desc">{{goods.tag}}</p>
<p class="price">&yen;{{goods.price}}</p>
<div class="extra">
<RouterLink to="/">
<span>找相似</span>
<span>发现现多宝贝 &gt;</span>
</RouterLink>
</div>
</div>
</template>
<script>
export default {
name: 'HomeGoods',
props: {
goods: {
type: Object,
default: () => {}
}
}
}
</script>
<style scoped lang='less'>
.goods-item {
width: 240px;
height: 300px;
padding: 10px 30px;
position: relative;
overflow: hidden;
border: 1px solid transparent;
transition: all .5s;
.image {
display: block;
width: 160px;
height: 160px;
margin: 0 auto;
img {
width: 100%;
height: 100%;
}
}
p {
margin-top: 6px;
font-size: 16px;
&.name {
height: 44px;
}
&.desc {
color: #666;
height: 22px;
}
&.price {
margin-top: 10px;
font-size: 20px;
color: #CF4444;
}
}
.extra {
position: absolute;
left: 0;
bottom: 0;
height: 86px;
width: 100%;
background: #27BA9B;
text-align: center;
transform: translate3d(0,100%,0);
transition: all .5s;
span {
display: block;
color: #fff;
width: 120px;
margin: 0 auto;
line-height: 30px;
&:first-child {
font-size: 18px;
border-bottom:1px solid #fff;
line-height: 40px;
margin-top: 5px;
}
}
}
&:hover {
border-color: #27BA9B;
.extra {
transform: none;
}
}
}
</style>
<template>
<HomePanel title="人气推荐" sub-title="人气爆款 不容错过">
<ul ref="pannel" class="goods-list">
<li v-for="item in goods" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
<p class="name">{{item.title}}</p>
<p class="desc">{{item.alt}}</p>
</RouterLink>
</li>
</ul>
</HomePanel>
</template>
<script>
import { ref } from 'vue'
import HomePanel from './home-panel'
import { findHot } from '@/api/home'
export default {
name: 'HomeNew',
components: { HomePanel },
setup () {
const goods = ref([])
findHot().then(data => {
goods.value = data.result
})
return { goods }
}
}
</script>
<style scoped lang='less'>
@import '@/assets/styles/mixins.less';
@import '@/assets/styles/variables.less';
.goods-list {
display: flex;
justify-content: space-between;
height: 426px;
li {
width: 306px;
height: 406px;
.hoverShadow();
img {
width: 306px;
height: 306px;
}
p {
font-size: 22px;
padding-top: 12px;
text-align: center;
}
.desc {
color: #999;
font-size: 18px;
}
}
}
</style>
<template>
<div class="home-new">
<HomePanel title="新鲜好物" sub-title="新鲜出炉 品质靠谱">
<template #right>
<XtxMore path="/"/>
</template>
<!-- 面板内容 -->
<div ref="target" style="position: relative;height: 406px;">
<Transition name="fade">
<ul v-if="goods.length" ref="pannel" class="goods-list">
<li v-for="item in goods" :key="item.id">
<RouterLink :to="`/product/${item.id}`">
<img :src="item.picture" alt="">
<p class="name ellipsis">{{ item.name }}</p>
<p class="price">&yen;{{ item.price }}</p>
</RouterLink>
</li>
</ul>
<HomeSkeleton bg="#f0f9f4" v-else/>
</Transition>
</div>
</HomePanel>
</div>
</template>
<script>
import HomePanel from './home-panel'
import { findNew } from '@/api/home'
import HomeSkeleton from './home-skeleton'
import XtxMore from '@/components/library/xtx-more'
import { useLazyData } from '@/hooks'
export default {
name: 'HomeNew',
components: {
HomePanel,
HomeSkeleton,
XtxMore
},
setup () {
const {
target,
result
} = useLazyData(findNew)
return {
goods: result,
target
}
}
}
</script>
<style scoped lang="less">
// 全局引入不行那就局部引入咯 无语住
@import '@/assets/styles/mixins.less';
@import '@/assets/styles/variables.less';
.goods-list {
display: flex;
justify-content: space-between;
height: 406px;
li {
width: 306px;
height: 406px;
background: #f0f9f4;
.hoverShadow();
img {
width: 306px;
height: 306px;
}
p {
font-size: 22px;
padding: 12px 30px 0 30px;
text-align: center;
}
.price {
color: @priceColor;
}
}
}
</style>
<template>
<div class="home-panel">
<div class="container">
<div class="head">
<h3>{{ title }}<small>{{ subTitle }}</small></h3>
<!-- 右上角插槽-->
<slot name="right"/>
</div>
<!-- 默认插槽 面板内容-->
<slot/>
</div>
</div>
</template>
<script>
export default {
name: 'HomePanel',
props: {
// 标题
title: {
type: String,
default: ''
},
// 副标题
subTitle: {
type: String,
default: ''
}
}
}
</script>
<style scoped lang='less'>
.home-panel {
background-color: #fff;
.head {
padding: 40px 0;
display: flex;
align-items: flex-end;
h3 {
flex: 1;
font-size: 32px;
font-weight: normal;
margin-left: 6px;
height: 35px;
line-height: 35px;
small {
font-size: 16px;
color: #999;
margin-left: 20px;
}
}
}
}
</style>
<template>
<div class="home-product" ref="target">
<HomePanel :title="cate.name" v-for="cate in list" :key="cate.id">
<template v-slot:right>
<div class="sub">
<RouterLink v-for="sub in cate.children" :key="sub.id" :to="`/category/sub/${sub.id}`">{{sub.name}}</RouterLink>
</div>
<XtxMore :path="`/category/${cate.id}`" />
</template>
<div class="box">
<RouterLink class="cover" :to="`/category/${cate.id}`">
<img :src="cate.picture" alt="">
<strong class="label">
<span>{{cate.name}}馆</span>
<span>{{cate.saleInfo}}</span>
</strong>
</RouterLink>
<ul class="goods-list">
<li v-for="item in cate.goods" :key="item.id">
<HomeGoods :goods="item" />
</li>
</ul>
</div>
</HomePanel>
</div>
</template>
<script>
import HomePanel from './home-panel'
import HomeGoods from './home-goods'
import XtxMore from '@/components/library/xtx-more'
import { findGoods } from '@/api/home'
import { useLazyData } from '@/hooks'
export default {
name: 'HomeProduct',
components: { HomePanel, HomeGoods, XtxMore },
setup () {
const { target, result } = useLazyData(findGoods)
return { target, list: result }
}
}
</script>
<style scoped lang='less'>
.home-product {
background: #fff;
height: 2900px;
.sub {
margin-bottom: 2px;
a {
padding: 2px 12px;
font-size: 16px;
border-radius: 4px;
&:hover {
background: #27BA9B;
color: #fff;
}
&:last-child {
margin-right: 80px;
}
}
}
.box {
display: flex;
.cover {
width: 240px;
height: 610px;
margin-right: 10px;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.label {
width: 188px;
height: 66px;
display: flex;
font-size: 18px;
color: #fff;
line-height: 66px;
font-weight: normal;
position: absolute;
left: 0;
top: 50%;
transform: translate3d(0,-50%,0);
span {
text-align: center;
&:first-child {
width: 76px;
background: rgba(0,0,0,.9);
}
&:last-child {
flex: 1;
background: rgba(0,0,0,.7);
}
}
}
}
.goods-list {
width: 990px;
display: flex;
flex-wrap: wrap;
li {
width: 240px;
height: 300px;
margin-right: 10px;
margin-bottom: 10px;
&:nth-last-child(-n+4) {
margin-bottom: 0;
}
&:nth-child(4n) {
margin-right: 0;
}
}
}
}
}
</style>
<template>
<div class='home-skeleton'>
<div class="item" v-for="i in 4" :key="i" :style="{backgroundColor:bg}">
<XtxSkeleton bg="#e4e4e4" width="306px" height="306px" animated/>
<XtxSkeleton bg="#e4e4e4" width="160px" height="24px" animated/>
<XtxSkeleton bg="#e4e4e4" width="120px" height="24px" animated/>
</div>
</div>
</template>
<script>
import XtxSkeleton from '@/components/library/xtx-skeleton'
export default {
name: 'HomeSkeleton',
components: { XtxSkeleton },
props: {
bg: {
type: String,
default: '#fff'
}
}
}
</script>
<style scoped lang='less'>
.home-skeleton {
width: 1240px;
height: 406px;
display: flex;
justify-content: space-between;
.item {
width: 306px;
.xtx-skeleton ~ .xtx-skeleton {
display: block;
margin: 16px auto 0;
}
}
}
</style>
<template>
<!-- 首页入口-->
<div class="home-entry">
<div class="container">
<!-- 左侧分类 -->
<HomeCategory/>
<!-- 轮播图 -->
<HomeBanner/>
</div>
<!-- 新鲜好物-->
<HomeNew/>
<!-- 人气推荐 -->
<HomeHot/>
<!-- 热门品牌 -->
<HomeBrand/>
<!-- 商品区域 -->
<HomeProduct/>
<!-- 最新专题 -->
<!-- <HomeSpecial /> -->
</div>
</template>
<script>
import HomeCategory from './components/home-category.vue'
import HomeBanner from './components/home-banner.vue'
import HomeNew from './components/home-new.vue'
import HomeHot from './components/home-hot.vue'
import HomeBrand from './components/home-brand.vue'
import HomeProduct from './components/home-product.vue'
// import HomeSpecial from './components/home-special.vue'
export default {
name: 'PageHome',
components: {
HomeCategory,
HomeBanner,
HomeNew,
HomeHot,
HomeBrand,
HomeProduct
// HomeSpecial
}
}
</script>
<style scoped lang="less">
.xtx-home-page {
.container {
width: 1240px;
margin: 0 auto;
position: relative;
}
}
</style>
const path = require('path')
module.exports = {
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [
path.join(__dirname, './src/assets/styles/variables.less'),
path.join(__dirname, './src/assets/styles/mixins.less')
],
lintOnSave: false
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册