chore: init read-star monorepo
This commit is contained in:
39
apps/admin/src/components/common/app-provider.vue
Normal file
39
apps/admin/src/components/common/app-provider.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { useDialog, useLoadingBar, useMessage, useNotification } from 'naive-ui'
|
||||
import { createTextVNode, defineComponent } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'AppProvider',
|
||||
})
|
||||
|
||||
const ContextHolder = defineComponent({
|
||||
name: 'ContextHolder',
|
||||
setup() {
|
||||
function register() {
|
||||
window.$loadingBar = useLoadingBar()
|
||||
window.$dialog = useDialog()
|
||||
window.$message = useMessage()
|
||||
window.$notification = useNotification()
|
||||
}
|
||||
|
||||
register()
|
||||
|
||||
return () => createTextVNode()
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NLoadingBarProvider>
|
||||
<NDialogProvider>
|
||||
<NNotificationProvider>
|
||||
<NMessageProvider>
|
||||
<ContextHolder />
|
||||
<slot />
|
||||
</NMessageProvider>
|
||||
</NNotificationProvider>
|
||||
</NDialogProvider>
|
||||
</NLoadingBarProvider>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
17
apps/admin/src/components/common/dark-mode-container.vue
Normal file
17
apps/admin/src/components/common/dark-mode-container.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'DarkModeContainer' })
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
interface Props {
|
||||
inverted?: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-container text-base-text transition-300" :class="{ 'bg-inverted text-#1f1f1f': inverted }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
45
apps/admin/src/components/common/exception-base.vue
Normal file
45
apps/admin/src/components/common/exception-base.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({ name: 'ExceptionBase' })
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
type ExceptionType = '403' | '404' | '500'
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Exception type
|
||||
*
|
||||
* - 403: no permission
|
||||
* - 404: not found
|
||||
* - 500: service error
|
||||
*/
|
||||
type: ExceptionType
|
||||
}
|
||||
|
||||
const { routerPushByKey } = useRouterPush()
|
||||
|
||||
const iconMap: Record<ExceptionType, string> = {
|
||||
403: 'no-permission',
|
||||
404: 'not-found',
|
||||
500: 'service-error',
|
||||
}
|
||||
|
||||
const icon = computed(() => iconMap[props.type])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="size-full min-h-520px flex-col-center gap-24px overflow-hidden">
|
||||
<div class="flex text-400px text-primary">
|
||||
<SvgIcon :local-icon="icon" />
|
||||
</div>
|
||||
<NButton type="primary" @click="routerPushByKey('root')">
|
||||
{{ $t('common.backToHome') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
22
apps/admin/src/components/common/full-screen.vue
Normal file
22
apps/admin/src/components/common/full-screen.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({
|
||||
name: 'FullScreen',
|
||||
})
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
interface Props {
|
||||
full?: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonIcon :key="String(full)" :tooltip-content="full ? $t('icon.fullscreenExit') : $t('icon.fullscreen')">
|
||||
<icon-gridicons-fullscreen-exit v-if="full" />
|
||||
<icon-gridicons-fullscreen v-else />
|
||||
</ButtonIcon>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
42
apps/admin/src/components/common/icon-tooltip.vue
Normal file
42
apps/admin/src/components/common/icon-tooltip.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PopoverPlacement } from 'naive-ui'
|
||||
import { computed, useSlots } from 'vue'
|
||||
|
||||
defineOptions({ name: 'IconTooltip' })
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
icon: 'mdi-help-circle',
|
||||
localIcon: '',
|
||||
desc: '',
|
||||
placement: 'top',
|
||||
})
|
||||
|
||||
interface Props {
|
||||
icon?: string
|
||||
localIcon?: string
|
||||
desc?: string
|
||||
placement?: PopoverPlacement
|
||||
}
|
||||
|
||||
const slots = useSlots()
|
||||
const hasCustomTrigger = computed(() => Boolean(slots.trigger))
|
||||
|
||||
if (!hasCustomTrigger.value && !props.icon && !props.localIcon) {
|
||||
throw new Error('icon or localIcon is required when no custom trigger slot is provided')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NTooltip :placement="placement">
|
||||
<template #trigger>
|
||||
<slot name="trigger">
|
||||
<div class="cursor-pointer">
|
||||
<SvgIcon :icon="icon" :local-icon="localIcon" />
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<slot>
|
||||
<span>{{ desc }}</span>
|
||||
</slot>
|
||||
</NTooltip>
|
||||
</template>
|
||||
62
apps/admin/src/components/common/lang-switch.vue
Normal file
62
apps/admin/src/components/common/lang-switch.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({
|
||||
name: 'LangSwitch',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showTooltip: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
interface Props {
|
||||
/** Current language */
|
||||
lang: App.I18n.LangType
|
||||
/** Language options */
|
||||
langOptions: App.I18n.LangOption[]
|
||||
/** Show tooltip */
|
||||
showTooltip?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'changeLang', lang: App.I18n.LangType): void
|
||||
}
|
||||
|
||||
const tooltipContent = computed(() => {
|
||||
if (!props.showTooltip)
|
||||
return ''
|
||||
|
||||
return $t('icon.lang')
|
||||
})
|
||||
|
||||
/** Add bottom margin to all options except the last one for proper visual separation */
|
||||
const dropdownOptions = computed(() => {
|
||||
const lastIndex = props.langOptions.length - 1
|
||||
|
||||
return props.langOptions.map((option, index) => ({
|
||||
...option,
|
||||
props: {
|
||||
class: index < lastIndex ? 'mb-1' : undefined,
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
function changeLang(lang: App.I18n.LangType) {
|
||||
emit('changeLang', lang)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDropdown :value="lang" :options="dropdownOptions" trigger="hover" @select="changeLang">
|
||||
<div>
|
||||
<ButtonIcon :tooltip-content="tooltipContent" tooltip-placement="left">
|
||||
<SvgIcon icon="heroicons:language" />
|
||||
</ButtonIcon>
|
||||
</div>
|
||||
</NDropdown>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
53
apps/admin/src/components/common/menu-toggler.vue
Normal file
53
apps/admin/src/components/common/menu-toggler.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({ name: 'MenuToggler' })
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
arrowIcon: false,
|
||||
zIndex: 98,
|
||||
})
|
||||
|
||||
interface Props {
|
||||
/** Show collapsed icon */
|
||||
collapsed?: boolean
|
||||
/** Arrow style icon */
|
||||
arrowIcon?: boolean
|
||||
zIndex?: number
|
||||
}
|
||||
|
||||
type NumberBool = 0 | 1
|
||||
|
||||
const icon = computed(() => {
|
||||
const icons: Record<NumberBool, Record<NumberBool, string>> = {
|
||||
0: {
|
||||
0: 'line-md:menu-fold-left',
|
||||
1: 'line-md:menu-fold-right',
|
||||
},
|
||||
1: {
|
||||
0: 'ph-caret-double-left-bold',
|
||||
1: 'ph-caret-double-right-bold',
|
||||
},
|
||||
}
|
||||
|
||||
const arrowIcon = Number(props.arrowIcon || false) as NumberBool
|
||||
|
||||
const collapsed = Number(props.collapsed || false) as NumberBool
|
||||
|
||||
return icons[arrowIcon][collapsed]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonIcon
|
||||
:key="String(collapsed)"
|
||||
:tooltip-content="collapsed ? $t('icon.expand') : $t('icon.collapse')"
|
||||
tooltip-placement="bottom-start"
|
||||
:z-index="zIndex"
|
||||
>
|
||||
<SvgIcon :icon="icon" />
|
||||
</ButtonIcon>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
26
apps/admin/src/components/common/pin-toggler.vue
Normal file
26
apps/admin/src/components/common/pin-toggler.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({ name: 'PinToggler' })
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
interface Props {
|
||||
pin?: boolean
|
||||
}
|
||||
|
||||
const icon = computed(() => (props.pin ? 'mdi-pin-off' : 'mdi-pin'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonIcon
|
||||
:tooltip-content="pin ? $t('icon.unpin') : $t('icon.pin')"
|
||||
tooltip-placement="bottom-start"
|
||||
:z-index="100"
|
||||
>
|
||||
<SvgIcon :icon="icon" />
|
||||
</ButtonIcon>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
21
apps/admin/src/components/common/reload-button.vue
Normal file
21
apps/admin/src/components/common/reload-button.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({
|
||||
name: 'ReloadButton',
|
||||
})
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonIcon :tooltip-content="$t('icon.reload')">
|
||||
<icon-ant-design-reload-outlined :class="{ 'animate-spin animate-duration-750': loading }" />
|
||||
</ButtonIcon>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
160
apps/admin/src/components/common/system-logo.vue
Normal file
160
apps/admin/src/components/common/system-logo.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="app-logo">
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
version="1.1"
|
||||
viewBox="0 0 1000 1000"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M 200,866 C 100,866 50,779.4 100,692.8 L 200,519.6 C 220,485 240,490 265,499.6 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
|
||||
fill="url(#LinearGradient)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
d="M 420,866 C 455,861 478,846 500,827 C 614,696 615,597 500,517 C 394,444 333,374 380,207.82 L 260,415.67 C 240.22,450 254.37,465.1 275.28,481.79 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
|
||||
fill="url(#LinearGradient_2)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
d="M 500,517 C 394,444 333,374 380,207.82 L 400,173.2 C 367,295 421,350 603,428 C 572,440 524,474 500,517"
|
||||
fill="url(#LinearGradient_3)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 572,440 524,474 500,517 C 615,597 614,696 500,827"
|
||||
fill="url(#LinearGradient_4)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
d="M 400,173.2 C 367,295 421,350 603,428 C 690,389, 750,445 788,500 L 600,173.2 C 550,86.6 450,86.6 400,173.2"
|
||||
fill="url(#LinearGradient_5)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 690,389, 750,445 788,500 C 816,554 797,606 750,640 L 500,827"
|
||||
fill="url(#LinearGradient_6)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
d="M 788,500 C 816,554 797,606 750,640 L 500,827 C 497,851 513,862 540,866 L 800,866 C 900,866 950,779.4 900,692.8 L 788,500"
|
||||
fill="url(#LinearGradient_7)"
|
||||
fill-rule="nonzero"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="LinearGradient"
|
||||
gradientTransform="matrix(104.391 -73.3432 73.3432 104.391 277.441 710.122)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-700)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-600)" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="LinearGradient_2"
|
||||
gradientTransform="matrix(-173.747 557.324 -557.324 -173.747 508.829 258.172)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-300)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-500)" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="LinearGradient_3"
|
||||
gradientTransform="matrix(157.951 295.666 -295.666 157.951 382.944 193.642)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-600)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-700)" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="LinearGradient_4"
|
||||
gradientTransform="matrix(-44.3023 219.578 -219.578 -44.3023 619.69 469.652)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-400)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-600)" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="LinearGradient_5"
|
||||
gradientTransform="matrix(125.52 334.256 -334.256 125.52 539.723 235.139)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-500)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-300)" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="LinearGradient_6"
|
||||
gradientTransform="matrix(-241.23 357.206 -357.206 -241.23 754.054 449.312)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-300)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-500)" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="LinearGradient_7"
|
||||
gradientTransform="matrix(125.978 210.065 -210.065 125.978 596.433 613.665)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop offset="0" stop-color="var(--logo-color-700)" />
|
||||
<stop offset="1" stop-color="var(--logo-color-600)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-logo {
|
||||
--logo-color-300: rgb(var(--primary-300-color));
|
||||
--logo-color-400: rgb(var(--primary-400-color));
|
||||
--logo-color-500: rgb(var(--primary-500-color));
|
||||
--logo-color-600: rgb(var(--primary-600-color));
|
||||
--logo-color-700: rgb(var(--primary-700-color));
|
||||
}
|
||||
</style>
|
||||
57
apps/admin/src/components/common/theme-schema-switch.vue
Normal file
57
apps/admin/src/components/common/theme-schema-switch.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverPlacement } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
defineOptions({ name: 'ThemeSchemaSwitch' })
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showTooltip: true,
|
||||
tooltipPlacement: 'bottom',
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
interface Props {
|
||||
/** Theme schema */
|
||||
themeSchema: UnionKey.ThemeScheme
|
||||
/** Show tooltip */
|
||||
showTooltip?: boolean
|
||||
/** Tooltip placement */
|
||||
tooltipPlacement?: PopoverPlacement
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'switch'): void
|
||||
}
|
||||
|
||||
function handleSwitch() {
|
||||
emit('switch')
|
||||
}
|
||||
|
||||
const icons: Record<UnionKey.ThemeScheme, string> = {
|
||||
light: 'material-symbols:sunny',
|
||||
dark: 'material-symbols:nightlight-rounded',
|
||||
auto: 'material-symbols:hdr-auto',
|
||||
}
|
||||
|
||||
const icon = computed(() => icons[props.themeSchema])
|
||||
|
||||
const tooltipContent = computed(() => {
|
||||
if (!props.showTooltip)
|
||||
return ''
|
||||
|
||||
return $t('icon.themeSchema')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonIcon
|
||||
:icon="icon"
|
||||
:tooltip-content="tooltipContent"
|
||||
:tooltip-placement="tooltipPlacement"
|
||||
@click="handleSwitch"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user