初始化项目
This commit is contained in:
53
src/components/custom/better-scroll.vue
Normal file
53
src/components/custom/better-scroll.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import BScroll from '@better-scroll/core';
|
||||
import type { Options } from '@better-scroll/core';
|
||||
|
||||
defineOptions({ name: 'BetterScroll' });
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* BetterScroll options
|
||||
*
|
||||
* @link https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html
|
||||
*/
|
||||
options: Options;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const bsWrapper = ref<HTMLElement>();
|
||||
const bsContent = ref<HTMLElement>();
|
||||
const { width: wrapWidth } = useElementSize(bsWrapper);
|
||||
const { width, height } = useElementSize(bsContent);
|
||||
|
||||
const instance = ref<BScroll>();
|
||||
const isScrollY = computed(() => Boolean(props.options.scrollY));
|
||||
|
||||
function initBetterScroll() {
|
||||
if (!bsWrapper.value) return;
|
||||
instance.value = new BScroll(bsWrapper.value, props.options);
|
||||
}
|
||||
|
||||
// refresh BS when scroll element size changed
|
||||
watch([() => wrapWidth.value, () => width.value, () => height.value], () => {
|
||||
instance.value?.refresh();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
initBetterScroll();
|
||||
});
|
||||
|
||||
defineExpose({ instance });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="bsWrapper" class="h-full text-left">
|
||||
<div ref="bsContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
87
src/components/custom/button-icon.vue
Normal file
87
src/components/custom/button-icon.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import type { TooltipPlacement } from 'ant-design-vue/es/tooltip';
|
||||
|
||||
defineOptions({
|
||||
name: 'ButtonIcon',
|
||||
inheritAttrs: false
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** Button class */
|
||||
class?: string;
|
||||
/** Iconify icon name */
|
||||
icon?: string;
|
||||
/** Tooltip content */
|
||||
tooltipContent?: string;
|
||||
/** Tooltip placement */
|
||||
tooltipPlacement?: TooltipPlacement;
|
||||
/** Trigger tooltip on parent */
|
||||
triggerParent?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
class: 'h-36px text-icon',
|
||||
icon: '',
|
||||
tooltipContent: '',
|
||||
tooltipPlacement: 'bottom',
|
||||
triggerParent: false
|
||||
});
|
||||
|
||||
interface ButtonProps {
|
||||
className: string;
|
||||
}
|
||||
|
||||
const [DefineButton, Button] = createReusableTemplate<ButtonProps>();
|
||||
|
||||
const cls = computed(() => {
|
||||
let clsStr = props.class;
|
||||
|
||||
if (!clsStr.includes('h-')) {
|
||||
clsStr += ' h-36px';
|
||||
}
|
||||
|
||||
if (!clsStr.includes('text-')) {
|
||||
clsStr += ' text-icon';
|
||||
}
|
||||
|
||||
return clsStr;
|
||||
});
|
||||
|
||||
function getPopupContainer(triggerNode: HTMLElement) {
|
||||
return props.triggerParent ? triggerNode.parentElement! : document.body;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- define component start: Button -->
|
||||
<DefineButton v-slot="{ $slots, className }">
|
||||
<AButton type="text" :class="className">
|
||||
<div class="flex-center gap-8px">
|
||||
<component :is="$slots.default" />
|
||||
</div>
|
||||
</AButton>
|
||||
</DefineButton>
|
||||
<!-- define component end: Button -->
|
||||
|
||||
<ATooltip
|
||||
v-if="tooltipContent"
|
||||
:placement="tooltipPlacement"
|
||||
:get-popup-container="getPopupContainer"
|
||||
:title="tooltipContent"
|
||||
>
|
||||
<Button :class-name="cls" v-bind="$attrs">
|
||||
<slot>
|
||||
<SvgIcon :icon="icon" />
|
||||
</slot>
|
||||
</Button>
|
||||
</ATooltip>
|
||||
<Button v-else :class-name="cls" v-bind="$attrs">
|
||||
<slot>
|
||||
<SvgIcon :icon="icon" />
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
88
src/components/custom/count-to.vue
Normal file
88
src/components/custom/count-to.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { TransitionPresets, useTransition } from '@vueuse/core';
|
||||
|
||||
defineOptions({
|
||||
name: 'CountTo'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
startValue?: number;
|
||||
endValue?: number;
|
||||
duration?: number;
|
||||
autoplay?: boolean;
|
||||
decimals?: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
separator?: string;
|
||||
decimal?: string;
|
||||
useEasing?: boolean;
|
||||
transition?: keyof typeof TransitionPresets;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
startValue: 0,
|
||||
endValue: 2021,
|
||||
duration: 1500,
|
||||
autoplay: true,
|
||||
decimals: 0,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
separator: ',',
|
||||
decimal: '.',
|
||||
useEasing: true,
|
||||
transition: 'linear'
|
||||
});
|
||||
|
||||
const source = ref(props.startValue);
|
||||
|
||||
const transition = computed(() => (props.useEasing ? TransitionPresets[props.transition] : undefined));
|
||||
|
||||
const outputValue = useTransition(source, {
|
||||
disabled: false,
|
||||
duration: props.duration,
|
||||
transition: transition.value
|
||||
});
|
||||
|
||||
const value = computed(() => formatValue(outputValue.value));
|
||||
|
||||
function formatValue(num: number) {
|
||||
const { decimals, decimal, separator, suffix, prefix } = props;
|
||||
|
||||
let number = num.toFixed(decimals);
|
||||
number = String(number);
|
||||
|
||||
const x = number.split('.');
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? decimal + x[1] : '';
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (separator) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, `$1${separator}$2`);
|
||||
}
|
||||
}
|
||||
|
||||
return prefix + x1 + x2 + suffix;
|
||||
}
|
||||
|
||||
async function start() {
|
||||
await nextTick();
|
||||
source.value = props.endValue;
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => props.startValue, () => props.endValue],
|
||||
() => {
|
||||
if (props.autoplay) {
|
||||
start();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{ value }}</span>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
20
src/components/custom/look-forward.vue
Normal file
20
src/components/custom/look-forward.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'LookForward'
|
||||
});
|
||||
</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="expectation" />
|
||||
</div>
|
||||
<slot>
|
||||
<h3 class="text-28px text-primary font-500">{{ $t('common.lookForward') }}</h3>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
13
src/components/custom/soybean-avatar.vue
Normal file
13
src/components/custom/soybean-avatar.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SoybeanAvatar'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="size-72px overflow-hidden rd-1/2">
|
||||
<img src="@/assets/imgs/soybean.jpg" class="size-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
54
src/components/custom/svg-icon.vue
Normal file
54
src/components/custom/svg-icon.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useAttrs } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
|
||||
defineOptions({ name: 'SvgIcon', inheritAttrs: false });
|
||||
|
||||
/**
|
||||
* Props
|
||||
*
|
||||
* - Support iconify and local svg icon
|
||||
* - If icon and localIcon are passed at the same time, localIcon will be rendered first
|
||||
*/
|
||||
interface Props {
|
||||
/** Iconify icon name */
|
||||
icon?: string;
|
||||
/** Local svg icon name */
|
||||
localIcon?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const bindAttrs = computed<{ class: string; style: string }>(() => ({
|
||||
class: (attrs.class as string) || '',
|
||||
style: (attrs.style as string) || ''
|
||||
}));
|
||||
|
||||
const symbolId = computed(() => {
|
||||
const { VITE_ICON_LOCAL_PREFIX: prefix } = import.meta.env;
|
||||
|
||||
const defaultLocalIcon = 'no-icon';
|
||||
|
||||
const icon = props.localIcon || defaultLocalIcon;
|
||||
|
||||
return `#${prefix}-${icon}`;
|
||||
});
|
||||
|
||||
/** If localIcon is passed, render localIcon first */
|
||||
const renderLocalIcon = computed(() => props.localIcon || !props.icon);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="renderLocalIcon">
|
||||
<svg aria-hidden="true" width="1em" height="1em" v-bind="bindAttrs">
|
||||
<use :xlink:href="symbolId" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon v-if="icon" :icon="icon" v-bind="bindAttrs" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
59
src/components/custom/wave-bg.vue
Normal file
59
src/components/custom/wave-bg.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { getColorPalette } from '@sa/utils';
|
||||
|
||||
interface Props {
|
||||
/** Theme color */
|
||||
themeColor: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const lightColor = computed(() => getColorPalette(props.themeColor, 3));
|
||||
const darkColor = computed(() => getColorPalette(props.themeColor, 6));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute-lt z-1 size-full overflow-hidden">
|
||||
<div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
|
||||
<svg height="1337" width="1337">
|
||||
<defs>
|
||||
<path
|
||||
id="path-1"
|
||||
opacity="1"
|
||||
fill-rule="evenodd"
|
||||
d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"
|
||||
/>
|
||||
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
|
||||
<stop offset="0" :stop-color="lightColor" stop-opacity="1" />
|
||||
<stop offset="1" :stop-color="darkColor" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
|
||||
<svg height="896" width="967.8852157128662">
|
||||
<defs>
|
||||
<path
|
||||
id="path-2"
|
||||
opacity="1"
|
||||
fill-rule="evenodd"
|
||||
d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"
|
||||
/>
|
||||
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
|
||||
<stop offset="0" :stop-color="darkColor" stop-opacity="1" />
|
||||
<stop offset="1" :stop-color="lightColor" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user