导航
? Check the features needed for your project: Choose Vue version, Babel, Router, Vuex, CSS Pre-process
ors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS
(with node-sass)
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
? Save this as a preset for future projects? No
.
├── App.vue
├── assets
├── components
├── main.js
├── router
│ └── index.js
├── store
│ └── index.js
└── views
assets 资源.
├── css
│ ├── border.css # 移动端1px边框
│ └── resets.css # 标签重置
├── img
│ ├── error.jpg
│ ├── 中秋节.jpg
│ ├── 元旦.jpg
│ ├── 劳动节.jpg
│ ├── 国庆节.jpg
│ ├── 春节.jpg
│ ├── 清明节.jpg
│ ├── 端午节.jpg
│ └── 除夕.jpg
└── js
├── common.js # 初始化 fastclick;消除 touchmove 警告;移动端 rem 适配
└── fastclick.js
assets 资源导入 main.jsimport { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/css/resets.css';
import '@/assets/css/border.css';
import '@/assets/js/common.js';
createApp(App).use(store).use(router).mount('#app')
module.exports = {
devServer: {
overlay: { // 报错页面蒙层关闭
warning: false,
errors: false
},
proxy: {
'/api': { // 遇到接口为 /api 开头的,做以下代理配置
target: '<http://v.juhe.cn/>',
changeOrigin: true, // 改变源 进行跨域
ws: true, // 开启 websocket
secure: false, // https 检查关闭
pathRewrite: { // 路径重写
'^/api/': '', // 把 /api/myAPI/path 变为 myAPI/path, 然后代理到 <http://localhost:8080> 这个代理服务器下边
}
}
}
},
lintOnSave: false, // 关闭 eslint 检查
}
eslint 检查还得关闭 package.json 下的以下属性:
"eslintConfig": {
"root": false, // 设置 false
"env": {
"node": false // 设置 false
},
...
}
npm i axios qs -S
.
├── Day.vue
├── Month.vue
└── Year.vue
import { createRouter, createWebHashHistory } from 'vue-router'
import DayPage from '@/views/Day';
const routes = [
{
path: '/',
name: 'DayPage',
component: DayPage
},
{
path: '/month',
name: 'MonthPage',
component: () => import('@/views/Month.vue')
},
{
path: '/year',
name: 'YearPage',
component: () => import('@/views/Day.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
configs 文件夹const JUHE_APP_KEY = 'your api key';
export {
JUHE_APP_KEY
}
import axios from 'axios';
import qs from 'qs';
import { JUHE_APP_KEY } from '../../configs/keys';
function get(options) {
axios({
method: 'GET',
url: options.url,
params: {
...options.params,
key: JUHE_APP_KEY
}
}).then(res => {
options.success(res.data);
}, err => {
options.error(err);
});
}
function post(options) {
axios({
method: 'POST',
url: options.url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: qs.stringify({
...options.data,
key: JUHE_APP_KEY
})
}).then(res => {
options.success(res.data);
}, err => {
options.error(err);
});
}
export {
get,
post
}
import { get, post } from '../libs/http';
function getDayData(date) {
return new Promise((resolve, reject) => {
post({
url: '/api/calendar/day',
data: { date },
success(data) {
resolve(data);
},
error(err) {
reject(err);
}
})
});
}
function getMonthData(yearMonth) {
return new Promise((resolve, reject) => {
post({
url: '/api/calendar/month',
data: { 'year-month': yearMonth },
success(data) {
resolve(data);
},
error(err) {
reject(err);
}
})
});
}
function getYearData(year) {
return new Promise((resolve, reject) => {
get({
url: '/api/calendar/year',
params: { year },
success(data) {
resolve(data);
},
error(err) {
reject(err);
}
})
});
}
export {
getDayData,
getMonthData,
getYearData
}
import {
getDayData,
getMonthData,
getYearData
} from './request';
export default async (field, date) => {
let data = null;
switch (field) {
case 'day':
data = await getDayData(date);
break;
case 'month':
data = await getMonthData(date);
break;
case 'year':
data = await getYearData(date);
default:
break;
}
return data;
}
<template>
<div class="container">
Day Page
</div>
</template>
<script>
import { onMounted } from 'vue';
import getData from '@/services';
export default {
name: 'DayPage',
setup() {
onMounted(async () => {
const data = await getData('day', '2022-3-16');
console.log(data);
});
}
}
</script>
<style scoped>
</style>
components/Header/index.vue
<template>
<header class="header">
<h1>
<slot></slot>
</h1>
</header>
</template>
<script>
export default {
name: 'MyHeader',
}
</script>
<style lang="scss" scoped>
.header {
position: fixed;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: .44rem;
background-color: #ed4040;
text-align: center;
line-height: .44rem;
h1 {
font-size: .18rem;
color: #fff;
}
}
</style>
.
├── actions.js
├── index.js
├── mutations.js
└── state.js
state.js
export default {
headerTitle: '当天信息'
}
mutations.js
export default {
setHeaderTitle(state, routeName) { // 根据路由名称设置 header-title
switch (routeName) {
case 'day':
state.headerTitle = '当天信息';
break;
case 'month':
state.headerTitle = '近期假期';
break;
case 'year':
state.headerTitle = '当年假期';
break;
default:
state.headerTitle = '当天信息';
break;
}
}
}
<template>
<div id="app">
<my-header>{{ headerTitle }}</my-header>
<router-view/>
</div>
</template>
<script>
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { computed } from 'vue';
import MyHeader from '@/components/Header';
export default {
name: 'App',
components: {
MyHeader
},
setup() {
// use[] 的方式获取 store 和 router
const store = useStore(),
state = store.state,
router = useRouter();
router.push('/');
return computed(() => state).value;
}
}
</script>
<style lang="scss" scoped>
.container {
padding: .82rem 0 .44rem 0;
box-sizing: border-box;
}
</style>
<template>...</template>
<script>
export default {
name: 'App',
components: {
MyHeader,
Tab
},
setup() {
// use[] 的方式获取 store 和 router
const store = useStore(),
state = store.state,
router = useRouter();
router.push('/');
// 监听路由变化,动态设置 headerTitle
watch(() => {
return router.currentRoute.value.name
}, (newVal) => {
store.commit('setHeaderTitle', newVal);
});
return computed(() => state).value;
}
}
</script>
<style lang="scss" scoped>
.container {
padding: .82rem 0 .44rem 0;
box-sizing: border-box;
}
</style>
index.vue
<template>
<div class="tab">
<div class="tab-item"
v-for="(item, idx) of tabData"
:key="idx"
>
<tab-icon
:iconText="item.iconText"
:path="item.path"
>{{ item.tabText }}</tab-icon>
</div>
</div>
</template>
<script>
import TabIcon from './Icon';
import tabData from '../../data/tab';
import { reactive } from 'vue';
export default {
name: 'Tab',
components: {
TabIcon
},
setup() {
const state = reactive({
tabData
})
return {
...state
}
}
}
</script>
<style lang="scss" scoped>
.tab {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: .44rem;
display: flex;
flex-direction: row;
border-top: 1px solid #ddd;
background-color: #fff;
.tab-item {
display: flex;
justify-content: center;
align-items: center;
width: 33.33%;
height: 100%;
}
}
</style>
Icon.vue
<template>
<router-link :to="path" class="tab-icon">
<i class="icon">{{ iconText }}</i>
<p class="text">
<slot></slot>
</p>
</router-link>
</template>
<script>
export default {
name: 'TabIcon',
props: {
iconText: {
type: String,
default: ''
},
path: {
type: String,
default: '/'
}
}
}
</script>
<style lang="scss" scoped>
.tab-icon {
display: inline-block;
.icon {
display: flex;
justify-content: center;
align-items: center;
width: 0.25rem;
height: 0.25rem;
border-radius: 50%;
background-color: #ddd;
color: #999;
font-size: .12rem;
transition: all .5s;
}
.text {
font-size: .12rem;
text-align: center;
margin-top: .02rem;
color: #999;
transition: color .5s;
}
&.router-link-active {
.icon {
background-color: #ed4040;
color: #fff;
}
.text {
color: #ed4040;
}
}
}
</style>
import { getIconDate } from '@/libs/utils';
export default [
{
iconText: getIconDate('day'),
tabText: '当天',
path: '/'
},
{
iconText: getIconDate('month'),
tabText: '近期',
path: '/month'
},
{
iconText: getIconDate('year'),
tabText: '当年',
path: '/year'
}
]
function _addZero(value) {
return value < 10 ? ('0' + value) : value;
}
function getIconDate(type) {
const date = new Date();
switch (type) {
case 'day':
return _addZero(date.getDate().toString());
case 'month':
return _addZero((date.getMonth() + 1).toString());
case 'year':
return _addZero(date.getFullYear().toString().substring(2));
default:
return _addZero(date.getDay().toString());
}
}
export {
getIconDate
}
<template>
<div id="app">
<my-header>{{ headerTitle }}</my-header>
<router-view/>
<tab />
</div>
</template>
<script>
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { computed } from 'vue';
import MyHeader from '@/components/Header';
import Tab from '@/components/Tab';
export default {
name: 'App',
components: {
MyHeader,
Tab
},
setup() {
// use[] 的方式获取 store 和 router
const store = useStore(),
state = store.state,
router = useRouter();
router.push('/');
return computed(() => state).value;
}
}
</script>
<style lang="scss" scoped>
.container {
padding: .82rem 0 .44rem 0;
box-sizing: border-box;
}
</style>
index.vue
<template>
<div class="search-wrap">
<input
type="text"
:placeholder="placeholder"
:maxlength="maxlength"
:value="inputValue"
@input="searchData($event)"
>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: 'SearchInput',
props: {
placeholder: String,
maxlength: Number
},
setup() {
const inputValue = ref('');
const searchData = (e) => {
}
return {
inputValue,
searchData
}
}
}
</script>
<style lang="scss" scoped>
.search-wrap {
position: fixed;
top: .44rem;
left: 0;
z-index: 1;
width: 100%;
height: 0.38rem;
padding: .03rem .1rem;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
background-color: #fff;
input {
width: 100%;
height: 100%;
font-size: .14rem;
text-indent: 1rem;
border-radius: .03rem;
&:focus {
border-color: #ed4040;
box-shadow: 0 0 .02rem #ed4040;
transition: all .3s;
}
}
}
</style>
<template>
<div id="app">
<my-header>{{ headerTitle }}</my-header>
<search-input
:placeholder="placeholder"
:maxLength="maxLength"
></search-input>
<router-view/>
<tab />
</div>
</template>
<script>
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { computed, watch } from 'vue';
import MyHeader from '@/components/Header';
import Tab from '@/components/Tab';
import SearchInput from '@/components/SearchInput';
export default {
name: 'App',
components: {
MyHeader,
Tab,
SearchInput
},
setup() {
// use[] 的方式获取 store 和 router
const store = useStore(),
state = store.state,
router = useRouter();
router.push('/');
// 监听路由变化,动态设置 headerTitle
watch(() => {
return router.currentRoute.value.name
}, (newVal) => {
store.commit('setHeaderTitle', newVal);
store.commit('setMaxLength', newVal);
store.commit('setPlaceholder', newVal);
});
return computed(() => state).value;
}
}
</script>
<style lang="scss" scoped>
.container {
padding: .82rem 0 .44rem 0;
box-sizing: border-box;
}
</style>
DayPage 文件夹存放 day 路由子组件DayPage/Card.vue
<template>
<div class="card-wrapper">
<div class="card">
<div class="header">
<span class="weekday">{{ data.weekday }}</span>
</div>
<h1 class="text lunar">{{ data.lunar }}</h1>
<h2 class="text date">{{ data.date }}</h2>
</div>
</div>
</template>
<script>
export default {
name: 'DayCard',
props: {
data: Object
}
}
</script>
<style lang="scss" scoped>
.card-wrapper {
padding: 0.15rem;
box-sizing: border-box;
.card {
background: url('~@/assets/img/春节.jpg') no-repeat center/cover;
border-radius: .15rem;
overflow: hidden;
border: 1px solid #ddd;
color: #fff;
.header {
height: 0.35rem;
padding: 0.1rem;
box-sizing: border-box;
font-size: .14rem;
background-color: rgba(0, 0, 0, .3);
}
.text {
text-align: center;
margin: 0.3rem 0;
text-shadow: .02rem .05rem .05rem #666;
&.lunar {
color: #f75555;
font-size: .5rem;
font-weight: bold;
}
&.date {
font-size: .2rem;
}
}
}
}
</style>
为了格式化聚合api数据扩充 utils 方法:
...
function formatChsDate(date, type) {
const _arr = date.split('-'),
[year, month, day] =_arr;
switch (type) {
case 'day':
return `${year}年${month}月${day}日`;
case 'month':
return `${year}年${month}月`;
case 'year':
return `${year}年`;
default:
return `${year}年${month}月${day}日`;
}
}
function mapForChsDate(data, key) {
return data.map(item => {
item[key] = formatChsDate(item[key]);
return item;
});
}
function getNowDate(field) {
const date = new Date();
let year = date.getFullYear(),
month = date.getMonth() + 1,
day = date.getDate();
switch (field) {
case 'day':
return `${year}-${month}-${day}`;
case 'month':
return `${year}-${month}`;
case 'year':
return `${year}`;
default:
return `${year}-${month}-${day}`;
}
}
export {
getIconDate,
formatChsDate,
mapForChsDate,
getNowDate
}