Commit 93594a3f by wusiyi

feat: 新增帮助中心

parent 9fac4485
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 1024 1024">
<path fill="#faaa15" d="M928 161H699.2c-49.1 0-97.1 14.1-138.4 40.7L512 233l-48.8-31.3A255.2 255.2 0 0 0 324.8 161H96c-17.7 0-32 14.3-32 32v568c0 17.7 14.3 32 32 32h228.8c49.1 0 97.1 14.1 138.4 40.7l44.4 28.6c1.3.8 2.8 1.3 4.3 1.3s3-.4 4.3-1.3l44.4-28.6C602 807.1 650.1 793 699.2 793H928c17.7 0 32-14.3 32-32V193c0-17.7-14.3-32-32-32M404 553.5c0 4.1-3.2 7.5-7.1 7.5H211.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45zm0-140c0 4.1-3.2 7.5-7.1 7.5H211.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45zm416 140c0 4.1-3.2 7.5-7.1 7.5H627.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45zm0-140c0 4.1-3.2 7.5-7.1 7.5H627.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45z" />
</svg>
\ No newline at end of file
<template> <template>
<div class="header" :class="{ 'no-shadow': !shadow }" ref="header"> <div
<headerNavMobile v-if="$isMobile" :userInfo="userInfo"></headerNavMobile> class="header"
:class="{ shadow: !$route.path.includes('help') }"
ref="header">
<headHelper v-if="$route.path.includes('help')" />
<headerNavMobile
v-else-if="$isMobile"
:userInfo="userInfo"></headerNavMobile>
<div class="container" v-else> <div class="container" v-else>
<div class="logo"> <div class="logo">
<span class="logo_text"> <span class="logo_text">
...@@ -172,16 +178,14 @@ import hdSvg from '../assets/images/product/hd.svg' ...@@ -172,16 +178,14 @@ import hdSvg from '../assets/images/product/hd.svg'
import zxSvg from '../assets/images/product/zx.svg' import zxSvg from '../assets/images/product/zx.svg'
import usSvg from '../assets/images/product/us.svg' import usSvg from '../assets/images/product/us.svg'
import joinSvg from '../assets/images/product/join.svg' import joinSvg from '../assets/images/product/join.svg'
import headHelper from './headHelper.vue'
export default { export default {
props: {
shadow: { type: Boolean },
},
inject: { inject: {
scrollParent: 'scrollParent', scrollParent: 'scrollParent',
}, },
components: { components: {
headerNavMobile, headerNavMobile,
headHelper,
}, },
data() { data() {
return { return {
...@@ -363,6 +367,10 @@ export default { ...@@ -363,6 +367,10 @@ export default {
left: 0; left: 0;
} */ } */
.header {
background-color: #fff;
}
.user-name { .user-name {
margin-right: 10px; margin-right: 10px;
} }
...@@ -406,8 +414,8 @@ export default { ...@@ -406,8 +414,8 @@ export default {
} }
} }
.header.no-shadow { .header.shadow {
box-shadow: none !important; box-shadow: 0 1px 12px 0 rgba(129, 129, 129, 0.1);
} }
.navbar { .navbar {
flex: 1; flex: 1;
......
<template>
<div>
<div class="top pt-3 pl-5 flex justify-between">
<div class="flex items-center gap-2 cursor-pointer" @click="goHelp">
<img class="h-8" :src="Logo" alt="logo" />
<h2 class="text-white text-2xl font-bold mt-2">九猫帮助中心</h2>
</div>
<div class="flex items-center">
<div
v-for="nav in navs"
:key="nav.path"
class="flex flex-col items-center justify-center mr-10">
<a class="nav-link relative" :index="nav.path" :href="nav.path">
{{ nav.name }}
</a>
<div class="nav-line"></div>
</div>
<el-button plain class="mr-10 mb-2" size="medium">免费试用</el-button>
</div>
</div>
</div>
</template>
<script>
import Logo from '../assets/logo.png'
export default {
name: 'headHelper',
data() {
return {
Logo,
navs: [
{
name: '首页',
path: '/',
},
{
name: '价格',
path: '/price',
},
{
name: '关于九猫',
path: '/about',
},
],
}
},
methods: {
goHelp() {
if (this.$route.path === '/help/index') return
this.$router.push('/help')
},
},
}
</script>
<style scoped lang="scss">
.top {
background-color: var(--primary-color);
height: 60px;
img {
filter: brightness(0) invert(1);
}
}
.nav-link {
color: #fff;
font-size: 18px;
font-weight: 530;
display: inline-block;
transition: all 0.2s ease;
&:hover {
color: var(--secondary-text-color);
}
}
.nav-line {
width: 120%;
height: 2px;
background-image: linear-gradient(
to right,
#ffffff00 5%,
var(--secondary-color),
#ffffff00 95%
);
margin-top: 5px;
transform: scaleX(0);
transform-origin: center;
transition: transform 0.2s ease;
}
.nav-link:hover + .nav-line {
transform: scaleX(1);
}
::v-deep .el-button--default {
color: #fff;
background-color: var(--secondary-color);
border: none;
font-size: 16px;
font-weight: 500;
transition: all 0.2s ease;
border-radius: 8px;
&:hover {
color: var(--secondary-color);
}
}
</style>
...@@ -59,6 +59,8 @@ import { ...@@ -59,6 +59,8 @@ import {
Scrollbar, Scrollbar,
RadioButton, RadioButton,
Divider, Divider,
BreadcrumbItem,
Breadcrumb,
} from 'element-ui' } from 'element-ui'
Vue.prototype.$ELEMENT = { size: 'mini' } Vue.prototype.$ELEMENT = { size: 'mini' }
...@@ -97,6 +99,8 @@ Vue.use(RadioButton) ...@@ -97,6 +99,8 @@ Vue.use(RadioButton)
Vue.use(Cascader) Vue.use(Cascader)
Vue.use(Divider) Vue.use(Divider)
Vue.use(Tabs) Vue.use(Tabs)
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.prototype.$alert = MessageBox.alert Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$message = (message) => Vue.prototype.$message = (message) =>
typeof message === 'string' typeof message === 'string'
......
...@@ -44,11 +44,6 @@ const routes = [ ...@@ -44,11 +44,6 @@ const routes = [
name: 'demandPage', name: 'demandPage',
component: demandPage, component: demandPage,
}, },
{
path: '/help',
name: 'help',
component: helpPage,
},
{ {
path: '/product/production', path: '/product/production',
...@@ -89,6 +84,33 @@ const routes = [ ...@@ -89,6 +84,33 @@ const routes = [
path: '/help', path: '/help',
name: 'help', name: 'help',
component: helpPage, component: helpPage,
redirect: 'help/index',
children: [
{
path: 'index',
name: 'HelpIndex',
component: (resolve) =>
require(['../views/help/pages/index.vue'], resolve),
},
{
path: 'beginner',
name: 'HelpBeginner',
component: (resolve) =>
require(['../views/help/pages/Beginner/beginner.vue'], resolve),
},
{
path: 'artical1',
name: 'Artical1',
component: (resolve) =>
require(['../views/help/pages/Beginner/artical1.vue'], resolve),
},
{
path: 'artical2',
name: 'Artical2',
component: (resolve) =>
require(['../views/help/pages/Beginner/artical2.vue'], resolve),
},
],
}, },
], ],
}, },
......
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import pathModule from './path'
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
path: pathModule,
}, },
state: { state: {
userInfo: JSON.parse(JSON.stringify(localStorage.getItem('userInfo'))) || undefined, userInfo:
shopifyObj:{} JSON.parse(JSON.stringify(localStorage.getItem('userInfo'))) || undefined,
shopifyObj: {},
}, },
mutations: { mutations: {
setUserInfo(state, profile) { setUserInfo(state, profile) {
state.userInfo = profile state.userInfo = profile
}, },
setShopKey(state,data){ setShopKey(state, data) {
state.shopifyObj = data state.shopifyObj = data
} },
},
actions: {
}, },
actions: {},
}) })
// 默认的帮助中心路径
const DEFAULT_HELP_CENTER = { name: '帮助中心', path: 'index' }
// 确保数组第一个元素是帮助中心
const ensureHelpCenterFirst = (pathNames) => {
if (!pathNames || pathNames.length === 0) {
return [DEFAULT_HELP_CENTER]
}
// 如果第一个元素已经是帮助中心,直接返回
if (pathNames[0].name === '帮助中心') {
return pathNames
}
const filtered = pathNames.filter((item) => item.name !== '帮助中心')
// 将帮助中心添加到第一位
return [DEFAULT_HELP_CENTER, ...filtered]
}
// 从 localStorage 初始化 currentPathNames
const initCurrentPathNames = () => {
try {
const stored = localStorage.getItem('currentPathNames')
const parsed = stored ? JSON.parse(stored) : []
return ensureHelpCenterFirst(parsed)
} catch (e) {
return [DEFAULT_HELP_CENTER]
}
}
export default {
namespaced: true,
state: {
currentPathNames: initCurrentPathNames(),
},
mutations: {
setCurrentPathNames(state, pathNames) {
// 确保第一个元素是帮助中心
state.currentPathNames = ensureHelpCenterFirst(pathNames || [])
localStorage.setItem(
'currentPathNames',
JSON.stringify(state.currentPathNames)
)
},
clearCurrentPathNames(state) {
// 清除时保留帮助中心
state.currentPathNames = [DEFAULT_HELP_CENTER]
localStorage.setItem(
'currentPathNames',
JSON.stringify(state.currentPathNames)
)
},
},
getters: {
currentPathNames: (state) => state.currentPathNames,
},
actions: {
setCurrentPathNames({ commit }, pathNames) {
commit('setCurrentPathNames', pathNames)
},
clearCurrentPathNames({ commit }) {
commit('clearCurrentPathNames')
},
// 添加文章路径
addArticlePath({ state, commit }, { title, index }) {
// 构建文章路径
const articlePath = `/help/artical${index + 1}`
// 获取当前路径列表
const currentPathNames = state.currentPathNames || []
// 构建新的路径列表,移除可能已存在的相同文章路径,然后添加新路径
const filteredPaths = currentPathNames.filter(
(item) => !item.path.startsWith('/help/artical')
)
// 添加文章路径
const newPathNames = [
...filteredPaths,
{ name: title, path: articlePath },
]
// 更新 vuex
commit('setCurrentPathNames', newPathNames)
},
},
}
:root { :root {
--primary-color: #eca217; --primary-color: #eca217;
--secondary-color: #3f419e;
--secondary-color-lighter: #3032bd;
--border-color: #dfe7f9; --border-color: #dfe7f9;
--shadow-color: rgba(216, 187, 26, 0.32); --shadow-color: rgba(216, 187, 26, 0.32);
--background-color: #faf7f2b0; --background-color: #faf7f2b0;
--darker-background-color: #f5ede0b0; --darker-background-color: #f5ede0b0;
--light-color: #e9b95f; --light-color: #e9b95f;
--darker-text-color: #faaa15; --darker-text-color: #faaa15;
--secondary-text-color: #434999;
} }
@tailwind base; @tailwind base;
......
<template>
<div>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
v-for="(item, index) in breadList"
:key="item.path"
@click.native="handleBreadClick(index)">
<a href="javascript:void(0)">{{ item.name }}</a>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
name: 'Bread',
computed: {
breadList() {
// currentPathNames 已经包含了帮助中心作为第一项
return this.$store.getters['path/currentPathNames'] || []
},
},
methods: {
handleBreadClick(index) {
// 当点击面包屑时,更新路径列表
const currentPathNames = this.breadList
if (index >= 0 && index < currentPathNames.length) {
const clickedItem = currentPathNames[index]
const newPathNames = currentPathNames.slice(0, index + 1)
this.$store.dispatch('path/setCurrentPathNames', newPathNames)
this.$router.push(`/help/${clickedItem.path}`)
}
},
},
}
</script>
<template>
<div class="rounded-md bg-white p-5">
<div class="title flex place-content-between mb-5">
<div class="title-left flex items-center">
<img class="w-10 h-10" :src="beginnerLogo" alt="入门必看" />
<div class="text-lg font-bold ml-3">入门必看</div>
</div>
<div class="more flex justify-center items-center">
<div class="text-sm">查看更多</div>
<i class="el-icon-d-arrow-right"></i>
</div>
</div>
<div class="tabs flex gap-5">
<div
class="tab flex justify-center items-center text-white font-medium text-sm cursor-pointer"
v-for="(tab, index) in tabs"
@click="currentTab = index"
:key="tab.key">
{{ tab.name }}
</div>
</div>
<div class="tab-content grid grid-cols-3 gap-x-10 gap-y-8 mt-5">
<div v-for="content in tabs[currentTab].content" :key="content.title">
<a :href="content.url" class="content-title text-gray-500 ml-8">
{{ content.title }}
</a>
</div>
</div>
</div>
</template>
<script>
import beginnerLogo from '../../../assets/images/help/beginner.svg'
export default {
name: 'HomeBeginner',
data() {
return {
beginnerLogo,
tabs: [
{
name: '新手入门',
key: 'beginner',
content: [
{
title: '九猫erp是什么?',
url: '/help/artical1',
},
{
title: '如何使用九猫erp?',
url: '/help/artical2',
},
],
},
],
currentTab: 0,
}
},
}
</script>
<style scoped lang="scss">
.more {
color: var(--primary-color);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
color: var(--light-color);
}
}
.tab {
width: 100px;
height: 50px;
border-radius: 40px;
background-color: var(--primary-color);
}
.content-title {
position: relative;
transition: all 0.2s ease;
&::before {
content: '';
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
position: absolute;
left: -23px;
top: 8px;
background-color: var(--primary-color);
box-shadow: 0 0 0 5px #e6c07977;
}
&:hover {
color: var(--primary-color);
}
}
</style>
<template> <template>
<div> <div>
<div class="top h-50px"></div> <div class="top flex flex-col items-center">
<div class="text-white text-4xl font-bold">帮助中心</div>
<el-input
v-model="keyword"
class="w-1/3 mt-5 search-input"
placeholder="请输入关键词查询..."
@keyup.enter.native="search">
<el-button slot="append" icon="el-icon-search" @click="search" />
</el-input>
</div>
<div class="content p-5">
<sideNav />
<div
class="right-content w-full h-full p-5"
:class="{ 'rounded-md bg-white': $route.path !== '/help/index' }">
<Bread v-if="$route.path !== '/help/index'" />
<router-view></router-view>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import sideNav from './sideNav.vue'
import Bread from './components/bread.vue'
export default { export default {
name: 'helpPage', name: 'helpPage',
components: {
sideNav,
Bread,
},
data() {
return {
keyword: '',
}
},
methods: {
search() {
console.log(this.keyword)
},
},
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.top { .top {
background-color: var(--primary-color); background-color: var(--primary-color);
height: 200px; height: 150px;
}
::v-deep .search-input .el-input__inner {
background-color: #ffffff;
height: 45px;
line-height: 48px;
font-size: 16px;
}
::v-deep .el-input--mini {
font-size: 25px;
cursor: pointer;
color: #fff;
}
::v-deep .el-input-group__append {
color: #eceaea;
background-color: #3f419e;
border: none;
transition: all 0.2s ease;
&:hover {
background-color: var(--secondary-color-lighter);
}
}
.content {
width: 100%;
height: 100%;
display: flex;
background-color: var(--background-color);
} }
</style> </style>
<template>
<div>
<h2 class="text-xl font-bold my-10">{{ title }}</h2>
<div class="content">
<p>
<strong>九猫erp</strong>
是一款专为电商卖家设计的智能管理工具,它集成了订单管理、库存管理、物流管理、财务管理等多种功能,帮助卖家实现高效运营。
</p>
<p>
<strong>九猫erp</strong>
支持多平台接入,包括淘宝、天猫、京东、拼多多等主流电商平台,卖家可以一键同步订单、库存、物流信息,实现跨平台管理。
</p>
</div>
</div>
</template>
<script>
export default {
name: 'Artical1',
data() {
return {
title: '九猫erp是什么?',
}
},
created() {},
}
</script>
<style scoped lang="scss">
.artical1 {
width: 100%;
height: 100%;
}
</style>
<template>
<div>
<h2 class="text-xl font-bold my-10">{{ title }}</h2>
<div class="content">
<p>
<strong>九猫erp</strong>
是一款专为电商卖家设计的智能管理工具,它集成了订单管理、库存管理、物流管理、财务管理等多种功能,帮助卖家实现高效运营。
</p>
<p>
<strong>九猫erp</strong>
支持多平台接入,包括淘宝、天猫、京东、拼多多等主流电商平台,卖家可以一键同步订单、库存、物流信息,实现跨平台管理。
</p>
</div>
</div>
</template>
<script>
export default {
name: 'Artical2',
data() {
return {
title: '如何使用九猫erp?',
}
},
created() {},
}
</script>
<style scoped lang="scss">
.artical1 {
width: 100%;
height: 100%;
}
</style>
<template>
<div class="w-full h-full bg-white rounded-md">
<div class="text-xl font-bold my-10">{{ title }}</div>
<div class="content">
<a
v-for="(t, index) in titles"
:key="t"
class="content-title text-gray-600 ml-8 block mb-3 pb-3 border-b border-gray-200"
@click="goArtical(t, index)"
:href="`/help/artical${index + 1}`">
{{ t }}
</a>
</div>
</div>
</template>
<script>
export default {
components: {},
data() {
return {
title: '',
titles: [],
}
},
created() {
const currentPathNames = this.$store.getters['path/currentPathNames']
this.title = currentPathNames[currentPathNames.length - 1].name
try {
const ctx = require.context('./', false, /\.vue$/)
ctx.keys().forEach((key) => {
if (/beginner\.vue$/i.test(key)) return
const mod = ctx(key)
const comp = mod && (mod.default || mod)
if (comp && typeof comp.data === 'function') {
const dataObj = comp.data.call({}) || {}
if (dataObj.title) {
this.titles.push(dataObj.title)
}
}
})
} catch (e) {
// ignore
}
},
methods: {
goArtical(title, index) {
// 调用 vuex action 添加文章路径
this.$store.dispatch('path/addArticlePath', { title, index })
},
},
}
</script>
<style scoped lang="scss">
.content-title {
position: relative;
transition: all 0.2s ease;
&::before {
content: '';
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
position: absolute;
left: -23px;
top: 10px;
background-color: var(--primary-color);
box-shadow: 0 0 0 5px #e6c07977;
}
&:hover {
color: var(--primary-color);
}
}
</style>
<template>
<div class="flex flex-col w-full gap-5">
<HomeNewer />
<HomeNewer />
<HomeNewer />
</div>
</template>
<script>
import HomeNewer from '../components/homeNewer.vue'
export default {
components: {
HomeNewer,
},
}
</script>
<template>
<div class="side-nav mr-5 rounded-md">
<el-input
class="mt-3 ml-5 w-4/5 mb-5"
v-model="searchKeyword"
placeholder="在目录中搜索..."></el-input>
<el-menu
@select="handleSelect"
:unique-opened="true"
:default-active="activeMenu">
<span v-for="item in menuList" :key="item.path">
<el-submenu
v-if="item.children && item.children.length > 0"
:index="item.path">
<template slot="title">
<span>{{ item.name }}</span>
</template>
<el-menu-item
v-for="child in item.children"
:key="child.path"
:index="child.path">
<span>{{ child.name }}</span>
</el-menu-item>
</el-submenu>
<el-menu-item v-else :key="item.path" :index="item.path">
<span>{{ item.name }}</span>
</el-menu-item>
</span>
</el-menu>
</div>
</template>
<script>
export default {
name: 'sideNav',
data() {
return {
searchKeyword: '',
activeMenu: '',
menuList: [
{
id: 1,
path: 'mustSee',
name: '入门必看',
children: [{ id: 11, path: 'beginner', name: '新手入门' }],
},
{ id: 2, path: 'pic', name: '图文教程' },
{ id: 3, path: 'video', name: '视频教程' },
{ id: 4, path: 'problem', name: '常见问题' },
{ id: 5, path: 'contact', name: '联系我们' },
],
}
},
mounted() {
this.syncMenuWithRoute()
},
watch: {
$route() {
this.syncMenuWithRoute()
},
},
methods: {
syncMenuWithRoute() {
const path = this.$route && this.$route.path ? this.$route.path : ''
const match = path.match(/^\/help\/([^/]+)/)
const currentKey = match ? match[1] : ''
this.activeMenu = currentKey
},
handleSelect(key, keyPath) {
if (key === this.activeMenu) return
const names = []
for (const item of this.menuList) {
if (item.path === keyPath[0]) {
names.push({ name: item.name, path: item.path })
if (keyPath[1] && Array.isArray(item.children)) {
for (const child of item.children) {
if (child.path === keyPath[1]) {
names.push({ name: child.name, path: child.path })
break
}
}
}
break
}
}
// 使用 vuex 保存路径名称
this.$store.dispatch('path/setCurrentPathNames', names)
const target = keyPath[1] || keyPath[0]
this.$router.push(`/help/${target}`)
},
},
}
</script>
<style scoped lang="scss">
.side-nav {
background-color: #ffffff;
height: 100%;
width: 13%;
}
::v-deep .el-input--mini .el-input__inner {
background-color: var(--background-color);
height: 30px;
font-size: 14px;
}
::v-deep .el-menu {
border-right: none;
}
</style>
...@@ -74,8 +74,6 @@ export default { ...@@ -74,8 +74,6 @@ export default {
.jomalls-home-page .header { .jomalls-home-page .header {
transition: all 0.3s; transition: all 0.3s;
border: 1px solid #eee;
background: #fff;
height: 60px; height: 60px;
position: sticky; position: sticky;
top: 0; top: 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment