yarn add -g @haha-cli/corehaha-cli --versionhaha-cli init hahaya //备注 上线仍有问题 暂不处理 先主要处理项目
Git Flow标准
根据需求,从 master 拉出分支
开发阶段,提交 commit
开发完毕,发起 PR(pull request)
代码评审
部署,测试
merge 到 master
分支命名
feature 开头代表新功能开发
hotfix 开头代表 bug 修复
安装第三方组件库 ant-design-vue
yarn add ant-design-vue
import { createApp } from 'vue'import App from './App.vue'import Antd from 'ant-design-vue'import 'ant-design-vue/dist/antd.css'createApp(App).use(Antd).mount('#app')
增加ts配置:https://juejin.cn/post/6982444129271676942
项目搭建
<template><div class="editor-container"><a-layout><a-layout-sider width="300" style="background: #fff"><div class="sidebar-container">组件列表</div></a-layout-sider><a-layout style="padding: 0 24px 24px"><a-layout-content class="preview-container"><p>画布区域</p><div class="preview-list" id="canvas-area"></div></a-layout-content></a-layout><a-layout-siderwidth="300"style="background: #fff"class="settings-panel">组件属性</a-layout-sider></a-layout></div></template><script lang="ts">import { defineComponent, computed } from 'vue'export default defineComponent({components: {},setup() {},})</script><style>.editor-container .preview-container {padding: 24px;margin: 0;min-height: 85vh;display: flex;flex-direction: column;align-items: center;position: relative;}.editor-container .preview-list {padding: 0;margin: 0;min-width: 375px;min-height: 200px;border: 1px solid #efefef;background: #fff;overflow-x: hidden;overflow-y: auto;position: fixed;margin-top: 50px;max-height: 80vh;}</style>
<template><a-layout><a-layout-header>乐高</a-layout-header><a-layout-content><div class="home-container"><template-list></template-list></div></a-layout-content><a-layout-footer>© 慕课网(imooc.com)版权所有</a-layout-footer></a-layout></template><script>import TemplateList from '@/components/TemplateList.vue'import { defineComponent } from 'vue'export default defineComponent({components: { TemplateList },setup() {},})</script><style>.ant-layout-header {color: white;}.page-title {color: #fff;}.home-container {background: #fff;padding: 0 24px 24px 30px;min-height: 85vh;max-width: 1200px;/* margin: 50px auto; */width: 100%;}</style>
<template><div class="homepage-container"><a-layout :style="{ background: '#fff' }"><a-layout-header class="header"><div class="page-title"><router-link to="/">慕课乐高</router-link></div><user-profile :user="user"></user-profile></a-layout-header><a-layout-content class="home-layout"><router-view></router-view></a-layout-content></a-layout><a-layout-footer>© 慕课网(imooc.com)版权所有 | 津ICP备20000929号-2</a-layout-footer></div></template><script lang="ts">import { computed, defineComponent } from 'vue'export default defineComponent({name: 'Index',components: {},setup() {},})</script><style>.header {display: flex;justify-content: space-between;align-items: center;}.page-title {color: #fff;}</style>
<template><div class="work-detail-container"><a-row type="flex" justify="center"><a-col :span="8" class="cover-img"><imgsrc="http://typescript-vue.oss-cn-beijing.aliyuncs.com/vue-marker/5f81cca3f3bf7a0e1ebaf885.png"alt=""/></a-col><a-col :span="8"><h2>11</h2><p>11</p><div class="author"><a-avatar>V</a-avatar>该模版由 <b>11</b> 创作</div><div class="bar-code-area"><span>扫一扫,手机预览</span><div ref="container"></div></div><div class="use-button"><a-button type="primary" size="large"> 使用模版 </a-button><a-button size="large"> 下载图片海报 </a-button></div></a-col></a-row></div></template><script lang="ts">import { defineComponent, computed } from 'vue'export default defineComponent({setup() {},})</script><style scoped>.work-detail-container {margin-top: 50px;}.cover-img {margin-right: 30px;}.cover-img img {width: 100%;}.use-button {margin: 30px 0;}.ant-avatar {margin-right: 10px;}.bar-code-area {margin: 20px 0;}</style>
<template><div class="template-list-component"><a-row :gutter="16"><a-col :span="6" class="poster-item"><a-card hoverable><template #cover><!-- <img :src="item.coverImg" v-if="item.coverImg" /> --><imgsrc="http://typescript-vue.oss-cn-beijing.aliyuncs.com/vue-marker/5f81cca3f3bf7a0e1ebaf885.png"/><div class="hover-item"><a-button size="large" type="primary">使用该模版创建</a-button></div></template><a-card-meta :title="11"><template v-slot:description><div class="description-detail"><span>作者:11</span><span class="user-number">11</span></div></template></a-card-meta></a-card></a-col></a-row></div></template><script lang="ts">import { defineComponent } from 'vue'export default defineComponent({name: 'template-list',})</script><style>.poster-item {position: relative;margin-bottom: 20px;}.poster-item .ant-card {border-radius: 12px;}.poster-item .ant-card-cover {height: 390px;}.poster-item .ant-card-cover > img {width: 100%;}.poster-item .ant-card-hoverable {box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.1);}.poster-item .ant-card-body {padding: 0;}.poster-item .ant-card-meta {margin: 0;}.poster-item .ant-card-meta-title {color: #333;padding: 10px 12px;border-bottom: 1px solid #f2f2f2;margin-bottom: 0 !important;}.description-detail {display: flex;justify-content: space-between;padding: 13px 12px;color: #999;}.user-number {font-weight: bold;}.poster-title {height: 70px;}.poster-title h2 {margin-bottom: 0px;}.poster-item .ant-card-cover {position: relative;overflow: hidden;border-top-left-radius: 12px;border-top-right-radius: 12px;}.poster-item .ant-card-cover img {transition: all ease-in 0.2s;}.poster-item .ant-card-cover .hover-item {position: absolute;left: 0;top: 0;width: 100%;height: 100%;display: none;background: rgba(0, 0, 0, 0.8);align-items: center;justify-content: center;border-top-left-radius: 12px;border-top-right-radius: 12px;}.poster-item:hover .hover-item {display: flex;}.poster-item:hover img {transform: scale(1.25);}.barcode-container img {border-radius: 0;}</style>
安装 vue-router 路由
import { createRouter, createWebHashHistory } from 'vue-router'const Home = () => import('@/views/Home.vue')const Editor = () => import('../views/Editor.vue')const TemplateDetail = () => import('../views/TemplateDetail.vue')const router = createRouter({history: createWebHashHistory(),routes: [{path: '/',name: 'home',component: Home,meta: {withFooter: true,},children: [//嵌套路由// {// path: '/',// name: 'home',// component: Home,// },],},{path: '/editor',name: 'editor',component: Editor,},{path: '/template/:id',name: 'template',component: TemplateDetail,meta: {withFooter: true,},},],})export default router
import { createApp } from 'vue'import App from './App.vue'import Antd from 'ant-design-vue'import 'ant-design-vue/dist/antd.css'import router from '@/routes/index'//将路由挂载到app实例上createApp(App).use(Antd).use(router).mount('#app')
在App.vue增加路由视图
<template><router-view /></template><script lang="ts">import { defineComponent } from 'vue'export default defineComponent({name: 'App',components: {},})</script><style>#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;}</style>
增加路由函数实现跳转
...//详情页点击详情的使用模板,跳转到编辑页面<router-link to="/editor"><a-button type="primary" size="large"> 使用模版 </a-button></router-link>...
...//首页点击使用该模版创建,跳转到详情页面<router-link :to="`/template/${1}`"><a-button size="large" type="primary">使用该模版创建</a-button></router-link>...
<template><div class="template-list-component"><pre>{{ route }}</pre>...</div></template><script lang="ts">import { defineComponent } from 'vue'// useRoute(获取路由参数)import { useRoute } from 'vue-router'export default defineComponent({name: 'template-list',setup() {const route = useRoute()return {route,}},})</script>...

...<script>import { useRouter } from 'vue-router'import TemplateList from '@/components/TemplateList.vue'import { defineComponent } from 'vue'export default defineComponent({components: { TemplateList },setup() {//useRouter 跳转const router = useRouter()setTimeout(() => {router.push(`/template/${1}`)}, 2000)},})</script>...
增加header是否根据不同页面显示显示
<template><a-layout><a-layout-header><span class="header">乐高</span></a-layout-header><a-layout-content><div class="content-container"><router-view /></div></a-layout-content><a-layout-footer v-if="withFooter">© 慕课网(imooc.com)版权所有</a-layout-footer></a-layout></template><script lang="ts">import { defineComponent, toRaw, computed } from 'vue'import { useRoute } from 'vue-router'export default defineComponent({name: 'app',setup() {const route = useRoute()const withFooter = computed(() => route.meta.withFooter)return {withFooter,}},})</script><style>.page-title {color: #fff;}.content-container {background: #fff;width: 100%;}.header {color: white;}</style>
安装vuex
路由
import { createRouter, createWebHashHistory } from 'vue-router'const Index = () => import('@/views/Index.vue')const Home = () => import('@/views/Home.vue')const Editor = () => import('../views/Editor.vue')const TemplateDetail = () => import('../views/TemplateDetail.vue')const router = createRouter({history: createWebHashHistory(),routes: [{path: '/',name: 'home',component: Index,children: [//嵌套路由{path: '',name: 'home',component: Home,},{path: '/template/:id',name: 'template',component: TemplateDetail,},],},{path: '/editor',name: 'editor',component: Editor,},],})export default router
store
export const LOGIN = 'login'export const LOGOUT = 'logout'export const GETTEMPLATEBYID = 'getTemplateById'
import { Module } from 'vuex'import { GloabalDataProps } from '.'import { GETTEMPLATEBYID } from './mutation-types'export interface TemplateProps {id: numbertitle: stringcoverImg: stringauthor: stringcopiedCount: number}export const testData: TemplateProps[] = [{id: 1,coverImg:'https://static.imooc-lego.com/upload-files/screenshot-889755.png',title: 'test title 1',author: 'viking',copiedCount: 1,},{id: 2,coverImg:'https://static.imooc-lego.com/upload-files/screenshot-677311.png',title: '前端架构师直播海报',author: 'viking',copiedCount: 1,},{id: 3,coverImg:'https://static.imooc-lego.com/upload-files/screenshot-682056.png',title: '前端架构师直播海报',author: 'viking',copiedCount: 1,},{id: 4,coverImg:'https://static.imooc-lego.com/upload-files/screenshot-677311.png',title: '前端架构师直播海报',author: 'viking',copiedCount: 1,},{id: 5,coverImg:'https://static.imooc-lego.com/upload-files/screenshot-889755.png',title: '前端架构师直播海报',author: 'viking',copiedCount: 1,},{id: 6,coverImg:'https://static.imooc-lego.com/upload-files/screenshot-677311.png',title: '前端架构师直播海报',author: 'viking',copiedCount: 1,},]export interface TemplatesProps {templateList: TemplateProps[]}//两个参数 第一个本地的interface,第二个是全局的interfaceconst templates: Module<TemplatesProps, GloabalDataProps> = {state: {templateList: testData,},getters: {[GETTEMPLATEBYID](state, getters, rootState) {return (templateId: number) =>state.templateList.find((item) => item.id === templateId)},},}export default templates
import { Module } from 'vuex'import { GloabalDataProps } from '.'import { LOGIN, LOGOUT } from './mutation-types'export interface UserProps {isLogin: booleanuserName?: string}const testUser: UserProps = { isLogin: false }//两个参数 第一个本地的interface,第二个是全局的interfaceconst user: Module<UserProps, GloabalDataProps> = {mutations: {[LOGIN](state) {state.isLogin = truestate.userName = 'hahaya'},[LOGOUT](state) {state.isLogin = false},},}export default user
import { createStore } from 'vuex'import user, { UserProps } from './user'import templates, { TemplatesProps } from './templates'export interface GloabalDataProps {user: UserPropstemplates: TemplatesProps}const store = createStore<GloabalDataProps>({modules: {user,templates,},})export default store
View
<template><div class="home-container"><template-list :templates="{ templates }"></template-list></div></template><script lang="ts">import { useRouter } from 'vue-router'import TemplateList from '@/components/TemplateList.vue'import { computed, defineComponent, toRaw } from 'vue'import { GloabalDataProps } from '@/store'import { useStore } from 'vuex'import { TemplateProps } from '@/store/templates'export default defineComponent({components: { TemplateList },setup() {const store = useStore<GloabalDataProps>()const templates = computed(() => store.state.templates.templateList)return {templates,}},})</script><style lang="less" scoped>.page-title {color: #fff;}.home-container {background: #fff;padding: 0 24px 24px 30px;min-height: 85vh;max-width: 1200px;/* margin: 50px auto; */width: 100%;}</style>
<template><a-layout><a-layout-header><router-link to="/">慕课乐高</router-link><user-profile :user="{ user }" /></a-layout-header><a-layout-content><div class="content-container"><router-view /></div></a-layout-content><a-layout-footer> © 慕课网(imooc.com)版权所有 </a-layout-footer></a-layout></template><script lang="ts">import { defineComponent, toRaw, computed } from 'vue'import { useRoute } from 'vue-router'import UserProfile from '@/components/UserProfile.vue'import { GloabalDataProps } from '@/store'import { useStore } from 'vuex'export default defineComponent({name: 'index',components: { UserProfile },setup() {const route = useRoute()const withHeader = computed(() => route.meta.withHeader)const store = useStore<GloabalDataProps>()const user = computed(() => store.state.user)return {withHeader,user: user,}},})</script><style lang="less" scoped>.content-container {width: 100%;padding: 20px;background: #fff;}.ant-layout-header {display: flex;justify-content: space-between;align-items: center;}</style>
<template><div class="work-detail-container"><a-row type="flex" justify="center"><a-col :span="8" class="cover-img"><img :src="template.coverImg" alt="" /></a-col><a-col :span="8"><h2>{{ template.title }}</h2><div class="author"><a-avatar>V</a-avatar>该模版由 <b>{{ template.author }}</b> 创作</div><div class="bar-code-area"><span>扫一扫,手机预览</span><div ref="container"></div></div><div class="use-button"><router-link to="/editor"><a-button type="primary" size="large"> 使用模版 </a-button></router-link><a-button size="large"> 下载图片海报 </a-button></div></a-col></a-row></div></template><script lang="ts">import { GloabalDataProps } from '@/store'import { GETTEMPLATEBYID } from '@/store/mutation-types'import { TemplateProps } from '@/store/templates'import { defineComponent, computed } from 'vue'import { useStore } from 'vuex'import { useRoute } from 'vue-router'export default defineComponent({setup() {const store = useStore<GloabalDataProps>()const route = useRoute()const template = computed<TemplateProps>(() =>store.getters[GETTEMPLATEBYID](Number(route.params.id)),)return {template,}},})</script><style lang="less" scoped>.work-detail-container {margin-top: 50px;}.cover-img {margin-right: 30px;}.cover-img img {width: 100%;}.use-button {margin: 30px 0;}.ant-avatar {margin-right: 10px;}.bar-code-area {margin: 20px 0;}</style>
components
<template><div class="template-list-component"><a-row :gutter="16"><a-col:span="6"class="poster-item"v-for="item in templates":key="item.id"><a-card hoverable><template #cover><img :src="item.coverImg" v-if="item.coverImg" /><imgv-elsesrc="http://typescript-vue.oss-cn-beijing.aliyuncs.com/vue-marker/5f81cca3f3bf7a0e1ebaf885.png"/><div class="hover-item"><router-link :to="`/template/${item.id}`"><a-button size="large" type="primary">使用该模版创建</a-button></router-link></div></template><a-card-meta :title="item.title"><template v-slot:description><div class="description-detail"><span>作者:{{ item.author }}</span><span class="user-number">{{ item.copiedCount }}</span></div></template></a-card-meta></a-card></a-col></a-row></div></template><script lang="ts">import { computed, defineComponent, PropType, reactive } from 'vue'// useRoute(获取路由参数)import { useRoute } from 'vue-router'import { TemplateProps, TemplatesProps } from '@/store/templates'export default defineComponent({name: 'template-list',props: {templates: {type: Object,required: true,},},setup(props) {const route = useRoute()const templates = computed<TemplateProps[]>(() => props.templates.templates)return {route,templates,}},})</script><style lang="less" scoped>.poster-item {position: relative;margin-bottom: 20px;}.poster-item .ant-card {border-radius: 12px;}.poster-item .ant-card-cover {height: 390px;}.poster-item .ant-card-cover > img {width: 100%;}.poster-item .ant-card-hoverable {box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.1);}.poster-item .ant-card-body {padding: 0;}.poster-item .ant-card-meta {margin: 0;}.poster-item .ant-card-meta-title {color: #333;padding: 10px 12px;border-bottom: 1px solid #f2f2f2;margin-bottom: 0 !important;}.description-detail {display: flex;justify-content: space-between;padding: 13px 12px;color: #999;}.user-number {font-weight: bold;}.poster-title {height: 70px;}.poster-title h2 {margin-bottom: 0px;}.poster-item .ant-card-cover {position: relative;overflow: hidden;border-top-left-radius: 12px;border-top-right-radius: 12px;}.poster-item .ant-card-cover img {transition: all ease-in 0.2s;}.poster-item .ant-card-cover .hover-item {position: absolute;left: 0;top: 0;width: 100%;height: 100%;display: none;background: rgba(0, 0, 0, 0.8);align-items: center;justify-content: center;border-top-left-radius: 12px;border-top-right-radius: 12px;}.poster-item:hover .hover-item {display: flex;}.poster-item:hover img {transform: scale(1.25);}.barcode-container img {border-radius: 0;}</style>
<template><a-buttontype="primary"class="user-profile-component"v-if="!user.isLogin"@click="login">登录</a-button><div v-else><a-dropdown-button class="user-profile-component" type="primary">{{ user.userName }}<template #overlay><a-menu class="user-profile-dropdown"><a-menu-item key="1" @click="logout"><user-outlined />退出登录</a-menu-item></a-menu></template></a-dropdown-button></div></template><script lang="ts">import { useStore } from 'vuex'import { computed, defineComponent, PropType } from 'vue'import { useRouter } from 'vue-router'import { UserProps } from '@/store/user'import { UserOutlined } from '@ant-design/icons-vue'import { message } from 'ant-design-vue'import { GloabalDataProps } from '@/store'export default defineComponent({name: 'user-profile',components: {UserOutlined,},props: {user: {type: Object,required: true,},},setup(props) {const store = useStore<GloabalDataProps>()const router = useRouter()//登录const login = () => {console.log()store.commit('login')message.success('登录成功')}//登出const logout = () => {store.commit('logout')message.success('退出登录成功,2秒后跳转到首页', 2)setTimeout(() => {router.replace('/')}, 2000)}return {user: props.user.user as UserProps,login,logout,}},})</script><style lang="less" scoped>.user-profile-dropdown {border-radius: 2px !important;}.user-operation > * {margin-left: 30px !important;}</style>
