Add support for collapse navigator and global config (#33)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "minor",
|
||||
"comment": "add config support global",
|
||||
"packageName": "@zhishuyun/hub",
|
||||
"email": "cqc@cuiqingcai.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
site: {
|
||||
logo: {
|
||||
url: 'https://hub.zhishuyun.com/assets/logo.da8de841.svg',
|
||||
width: 'auto',
|
||||
height: 'auto'
|
||||
}
|
||||
},
|
||||
apps: {
|
||||
chat: {
|
||||
enabled: true
|
||||
},
|
||||
midjourney: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
distribution: {
|
||||
inviterId: '123'
|
||||
}
|
||||
};
|
||||
@@ -27,7 +27,15 @@ export default defineComponent({
|
||||
return `${getBaseUrlAuth()}/auth/login?inviter_id=${this.inviterId}`;
|
||||
},
|
||||
inviterId() {
|
||||
const result = this.$route.query.inviter_id?.toString() || getCookie('INVITER_ID');
|
||||
// if forceInviterId is set, then use forceInviterId
|
||||
if (this.$config?.distribution?.forceInviterId) {
|
||||
return this.$config?.distribution?.defaultInviterId;
|
||||
}
|
||||
// Otherwise, use the inviter_id in the url, then use the inviter_id in the cookie, and finally use the default inviter_id
|
||||
const result =
|
||||
this.$route.query.inviter_id?.toString() ||
|
||||
getCookie('INVITER_ID') ||
|
||||
this.$config?.distribution?.defaultInviterId;
|
||||
return result;
|
||||
},
|
||||
authenticated() {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<font-awesome-icon :icon="icon" class="icon" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Chevron',
|
||||
components: {
|
||||
FontAwesomeIcon
|
||||
},
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon(): string {
|
||||
return `fa-solid fa-chevron-${this.direction}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon {
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 50%;
|
||||
padding: 5px 6px;
|
||||
cursor: pointer;
|
||||
color: var(--el-color-black);
|
||||
background: var(--el-color-white);
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,26 @@
|
||||
<template>
|
||||
<div class="navigator">
|
||||
<chevron v-if="collapsed" class="chevron" direction="right" @click="onOpenMenu"></chevron>
|
||||
<chevron v-else class="chevron" direction="left" @click="onCollapseMenu"></chevron>
|
||||
<div class="top">
|
||||
<div v-for="(link, linkIndex) in links" :key="linkIndex" class="link">
|
||||
<el-tooltip effect="dark" :content="link.displayName" placement="right">
|
||||
<div v-if="!collapsed">
|
||||
<img src="@/assets/images/logo.svg" class="logo" @click="onHome" />
|
||||
</div>
|
||||
<el-menu v-if="!collapsed" :default-active="activeIndex">
|
||||
<el-menu-item
|
||||
v-for="(link, linkIndex) in links"
|
||||
:key="linkIndex"
|
||||
:index="link.route.name"
|
||||
@click="$router.push(link.route)"
|
||||
>
|
||||
<font-awesome-icon :icon="link.icon" class="mr-2" />
|
||||
<template #title>{{ link.displayName }}</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<div v-for="(link, linkIndex) in links" v-else :key="linkIndex" class="link">
|
||||
<el-tooltip v-if="collapsed" effect="dark" :content="link.displayName" placement="right">
|
||||
<el-button
|
||||
:class="{
|
||||
button: true,
|
||||
active: link.routes.includes($route.name as string)
|
||||
}"
|
||||
:class="{button: true, active: link.routes.includes($route.name as string)}"
|
||||
@click="$router.push(link.route)"
|
||||
>
|
||||
<font-awesome-icon :icon="link.icon" />
|
||||
@@ -16,15 +29,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle" />
|
||||
<div class="bottom">
|
||||
<div class="link">
|
||||
<div v-if="!collapsed" class="bottom">
|
||||
<el-menu :default-active="activeIndex">
|
||||
<el-menu-item @click="onConsole">
|
||||
<font-awesome-icon icon="fa-solid fa-compass" class="mr-2" />
|
||||
<template #title>{{ $t('common.nav.console') }}</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item @click="onLogout">
|
||||
<font-awesome-icon icon="fa-solid fa-arrow-right-from-bracket" class="mr-2" />
|
||||
<template #title>{{ $t('common.nav.logOut') }}</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div v-else class="bottom">
|
||||
<div v-if="$config.navigation?.console" class="link">
|
||||
<el-tooltip effect="dark" :content="$t('common.nav.console')" placement="right">
|
||||
<el-button class="button" @click="onConsole">
|
||||
<font-awesome-icon icon="fa-solid fa-compass" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="link">
|
||||
<div v-if="$config.navigation?.help" class="link">
|
||||
<help-entry />
|
||||
</div>
|
||||
<div v-if="authenticated" class="link">
|
||||
@@ -40,16 +65,18 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { ElButton, ElTooltip } from 'element-plus';
|
||||
import { ElButton, ElTooltip, ElMenu, ElMenuItem } from 'element-plus';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import {
|
||||
ROUTE_CHAT_CONVERSATION,
|
||||
ROUTE_CHAT_CONVERSATION_NEW,
|
||||
ROUTE_CONSOLE_ROOT,
|
||||
ROUTE_INDEX,
|
||||
ROUTE_MIDJOURNEY_HISTORY,
|
||||
ROUTE_MIDJOURNEY_INDEX
|
||||
} from '@/router/constants';
|
||||
import HelpEntry from './HelpEntry2.vue';
|
||||
import Chevron from './Chevron.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Navigator',
|
||||
@@ -57,38 +84,68 @@ export default defineComponent({
|
||||
ElButton,
|
||||
HelpEntry,
|
||||
ElTooltip,
|
||||
FontAwesomeIcon
|
||||
FontAwesomeIcon,
|
||||
ElMenu,
|
||||
ElMenuItem,
|
||||
Chevron
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
links: [
|
||||
{
|
||||
route: {
|
||||
name: ROUTE_CHAT_CONVERSATION_NEW
|
||||
},
|
||||
displayName: this.$t('common.nav.chat'),
|
||||
icon: 'fa-regular fa-comment',
|
||||
image: 'https://cdn.zhishuyun.com/9ad12c99b2.png/thumb_100x100',
|
||||
routes: [ROUTE_CHAT_CONVERSATION, ROUTE_CHAT_CONVERSATION_NEW]
|
||||
const links = [];
|
||||
if (this.$config?.navigation?.chat) {
|
||||
links.push({
|
||||
route: {
|
||||
name: ROUTE_CHAT_CONVERSATION_NEW
|
||||
},
|
||||
{
|
||||
route: {
|
||||
name: ROUTE_MIDJOURNEY_INDEX
|
||||
},
|
||||
displayName: this.$t('common.nav.midjourney'),
|
||||
icon: 'fa-solid fa-palette',
|
||||
image: 'https://cdn.zhishuyun.com/83ee211091.png/thumb_100x100',
|
||||
routes: [ROUTE_MIDJOURNEY_INDEX, ROUTE_MIDJOURNEY_HISTORY]
|
||||
}
|
||||
]
|
||||
displayName: this.$t('common.nav.chat'),
|
||||
icon: 'fa-regular fa-comment',
|
||||
image: 'https://cdn.zhishuyun.com/9ad12c99b2.png/thumb_100x100',
|
||||
routes: [ROUTE_CHAT_CONVERSATION, ROUTE_CHAT_CONVERSATION_NEW]
|
||||
});
|
||||
}
|
||||
if (this.$config.navigation?.midjourney) {
|
||||
links.push({
|
||||
route: {
|
||||
name: ROUTE_MIDJOURNEY_INDEX
|
||||
},
|
||||
displayName: this.$t('common.nav.midjourney'),
|
||||
icon: 'fa-solid fa-palette',
|
||||
image: 'https://cdn.zhishuyun.com/83ee211091.png/thumb_100x100',
|
||||
routes: [ROUTE_MIDJOURNEY_INDEX, ROUTE_MIDJOURNEY_HISTORY]
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
links,
|
||||
activeIndex: this.$route.name as string
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
authenticated() {
|
||||
return !!this.$store.state.token.access;
|
||||
},
|
||||
collapsed: {
|
||||
get() {
|
||||
return this.$store.state.setting?.navigationCollapsed;
|
||||
},
|
||||
set(val: boolean) {
|
||||
this.$store.commit('setSetting', {
|
||||
navigationCollapsed: val
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onHome() {
|
||||
this.$router.push({
|
||||
name: ROUTE_INDEX
|
||||
});
|
||||
},
|
||||
async onOpenMenu() {
|
||||
this.collapsed = false;
|
||||
},
|
||||
async onCollapseMenu() {
|
||||
this.collapsed = true;
|
||||
},
|
||||
async onLogout() {
|
||||
await this.$store.dispatch('resetAll');
|
||||
await this.$store.dispatch('chat/resetAll');
|
||||
@@ -106,6 +163,31 @@ export default defineComponent({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.el-menu {
|
||||
width: 150px;
|
||||
border-right: none;
|
||||
.el-menu-item {
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.chevron {
|
||||
position: absolute;
|
||||
right: -12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scale(0.8);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80%;
|
||||
max-height: 40px;
|
||||
cursor: pointer;
|
||||
margin: 10px auto 20px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top,
|
||||
.bottom {
|
||||
@@ -115,7 +197,7 @@ export default defineComponent({
|
||||
.link {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 10px;
|
||||
margin: 0 10px 10px 10px;
|
||||
.button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="side-panel">
|
||||
<div>
|
||||
<img v-if="isOfficial" src="@/assets/images/logo.svg" class="logo" @click="onHome" />
|
||||
<img src="@/assets/images/logo.svg" class="logo" @click="onHome" />
|
||||
</div>
|
||||
<div class="links">
|
||||
<a
|
||||
@@ -75,14 +75,20 @@ export default defineComponent({
|
||||
text: this.$t('console.menu.orderList'),
|
||||
name: ROUTE_CONSOLE_ORDER_LIST,
|
||||
icon: 'fa-solid fa-store'
|
||||
},
|
||||
{
|
||||
}
|
||||
];
|
||||
|
||||
// if forcedInviterId is set, only the forced inviter can see the distribution menu
|
||||
// if forcedInviterId is not set, everyone can see the distribution menu
|
||||
if (!this.$config.distribution?.forceInviterId || this.user?.id === this.$config.distribution?.forceInviterId) {
|
||||
links.push({
|
||||
key: 'distribution-index',
|
||||
text: this.$t('console.menu.distributionIndex'),
|
||||
name: ROUTE_CONSOLE_DISTRIBUTION_INDEX,
|
||||
icon: 'fa-solid fa-coins'
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
export default {
|
||||
/**
|
||||
* The global configuration.
|
||||
*/
|
||||
global: {},
|
||||
|
||||
/**
|
||||
* The left navigation configuration.
|
||||
*/
|
||||
navigation: {
|
||||
/**
|
||||
* Show chat entry in left navigation.
|
||||
*/
|
||||
chat: true,
|
||||
|
||||
/**
|
||||
* Show midjourney entry in left navigation.
|
||||
*/
|
||||
midjourney: true,
|
||||
|
||||
/**
|
||||
* Show console entry in left navigation.
|
||||
*/
|
||||
console: true,
|
||||
|
||||
/**
|
||||
* Show help entry in left navigation.
|
||||
*/
|
||||
help: true
|
||||
},
|
||||
|
||||
/**
|
||||
* The features configuration.
|
||||
*/
|
||||
features: {
|
||||
/**
|
||||
* The chat feature config.
|
||||
*/
|
||||
chat: {},
|
||||
|
||||
/**
|
||||
* The midjourney feature config.
|
||||
*/
|
||||
midjourney: {}
|
||||
},
|
||||
|
||||
/**
|
||||
* The distribution configuration.
|
||||
*/
|
||||
distribution: {
|
||||
/**
|
||||
* The default inviter id to use when no inviter id (in url) is provided,
|
||||
*/
|
||||
defaultInviterId: undefined,
|
||||
|
||||
/**
|
||||
* Force the inviter id to be used, even if an inviter id is provided.
|
||||
* Note: if the forceInviterId is set, there will be no distribution page in console except for the forced inviter.
|
||||
*/
|
||||
forceInviterId: undefined
|
||||
}
|
||||
};
|
||||
Vendored
+3
@@ -13,3 +13,6 @@ declare module '*.vue' {
|
||||
const component: DefineComponent<{}, {}, {}>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
// declare config object
|
||||
declare const config: any;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
chat: 'AI 问答',
|
||||
midjourney: 'AI 绘画 - Midjourney',
|
||||
midjourney: 'AI 绘画',
|
||||
editor: '编辑器',
|
||||
setting: '设置',
|
||||
home: '首页',
|
||||
|
||||
@@ -53,7 +53,7 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
width: calc(100% - 60px);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.side {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="left">
|
||||
<navigator />
|
||||
</div>
|
||||
<navigator class="navigator" />
|
||||
<div class="main">
|
||||
<side-panel class="side" />
|
||||
<router-view class="operation" />
|
||||
@@ -46,11 +44,12 @@ export default defineComponent({
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.left {
|
||||
width: 60px;
|
||||
|
||||
.navigator {
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
width: calc(100% - 60px);
|
||||
|
||||
@@ -25,7 +25,6 @@ export default defineComponent({
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
.navigator {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: calc(100% - 60px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.menu {
|
||||
|
||||
@@ -13,6 +13,7 @@ import hl from 'highlight.js';
|
||||
import 'highlight.js/styles/night-owl.css';
|
||||
import copyToClipboard from 'copy-to-clipboard';
|
||||
import { initializeCookies } from './utils/initializer';
|
||||
import config from './plugins/config';
|
||||
|
||||
initializeCookies();
|
||||
|
||||
@@ -24,6 +25,7 @@ app.use(i18n);
|
||||
app.use(dayjs, {
|
||||
formatString: 'YYYY-MM-DD HH:mm:ss'
|
||||
});
|
||||
app.use(config);
|
||||
app.directive('loading', vLoading);
|
||||
app.mount('#app');
|
||||
console.debug('app mounted');
|
||||
@@ -43,3 +45,7 @@ app.directive('highlight', async (el) => {
|
||||
hl.highlightBlock(block);
|
||||
});
|
||||
});
|
||||
|
||||
// make app available globally
|
||||
// @ts-ignore
|
||||
window.app = app;
|
||||
|
||||
@@ -260,6 +260,9 @@ export default defineComponent({
|
||||
},
|
||||
distributionLink() {
|
||||
const origin = window.location.origin;
|
||||
if (!this.$store.getters.user?.id) {
|
||||
return origin;
|
||||
}
|
||||
return `${origin}?inviter_id=${this.$store.getters.user.id}`;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import config from '@/config';
|
||||
import { App } from 'vue';
|
||||
|
||||
export default {
|
||||
install: (app: App) => {
|
||||
app.config.globalProperties.$config = config;
|
||||
}
|
||||
};
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$config: typeof config;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
faClock as faRegularClock
|
||||
} from '@fortawesome/free-regular-svg-icons';
|
||||
import {
|
||||
faChevronRight as faSolidChevronRight,
|
||||
faChevronLeft as faSolidChevronLeft,
|
||||
faFire as faSolidFire,
|
||||
faBook as faSolidBook,
|
||||
faCompass as faSolidCompass,
|
||||
@@ -15,7 +17,6 @@ import {
|
||||
faPaperclip as faSolidPaperclip,
|
||||
faHashtag as faSolidHashtag,
|
||||
faCircleInfo as faSolidCircleInfo,
|
||||
faChevronLeft as faSolidChevronLeft,
|
||||
faMicrophone as faSolidMicrophone,
|
||||
faArrowLeft as faSolidArrowLeft,
|
||||
faUpload as faSolidUpload,
|
||||
@@ -55,6 +56,7 @@ import {
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
library.add(faRegularCopy);
|
||||
library.add(faSolidStore);
|
||||
library.add(faSolidChevronRight);
|
||||
library.add(faSolidCube);
|
||||
library.add(faSolidCompass);
|
||||
library.add(faSolidPaperclip);
|
||||
|
||||
@@ -15,7 +15,9 @@ export interface IToken {
|
||||
expiration?: number;
|
||||
}
|
||||
|
||||
export interface ISetting {}
|
||||
export interface ISetting {
|
||||
navigationCollapsed?: boolean;
|
||||
}
|
||||
|
||||
export interface ICommonState {
|
||||
token: IToken;
|
||||
|
||||
@@ -10,6 +10,10 @@ export default (): IRootState => {
|
||||
refresh: undefined,
|
||||
expiration: undefined
|
||||
},
|
||||
setting: {
|
||||
// if PC, set default to true, else false
|
||||
navigationCollapsed: window.innerWidth < 768
|
||||
},
|
||||
chat: chatState(),
|
||||
midjourney: midjourneyState()
|
||||
};
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": ["vite/client"]
|
||||
"types": ["vite/client", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user