feat(front): implement text sharing functionality with new TextShareHandle and TextShareResult components

This commit is contained in:
keven1024
2025-05-20 10:39:02 +08:00
parent 838d425369
commit c1fd4e7bbd
6 changed files with 227 additions and 41 deletions

View File

@@ -1,14 +1,18 @@
<script setup lang="ts">
import { LucideShare, LucideImage, LucideBot, LucideLanguages } from 'lucide-vue-next'
import { cx } from 'class-variance-authority'
import showDrawer from '~/lib/showDrawer';
import TextShareHandle from '~/components/Preprocessing/TextShareHandle.vue';
const props = defineProps<{
hide: () => void
text: string
onTextHandle: ({ type, config }: { type: string, config: any }) => void
}>()
const actions = [
{
label: '分享文本', icon: LucideShare, className: 'bg-green-300', onClick: () => {
console.log('复制链接')
showDrawer({ render: ({ hide }) => h(TextShareHandle, { ...props, hide }) })
}
},
{
@@ -31,9 +35,9 @@ const actions = [
<template>
<div class="flex flex-col gap-5 p-5">
<div class="flex flex-row gap-5">
<div v-for="item in actions" :key="item.label" class="flex flex-col items-center gap-2" @click="()=>{
item?.onClick()
<div v-for="item in actions" :key="item.label" class="flex flex-col items-center gap-2" @click="() => {
props?.hide()
item?.onClick()
}">
<div :class="cx('size-14 flex justify-center items-center rounded-full', item?.className)">
<component :is="item?.icon" />

View File

@@ -1,38 +1,32 @@
<script lang="ts" setup>
import { get } from 'lodash-es'
import VeeForm from '@/components/VeeForm.vue'
import MarkdownInputField from '@/components/Field/MarkdownInputField.vue'
import TextUploadInputTextView from './TextUploadInputTextView.vue'
import ResultIndexView from '@/components/Result/ResultIndexView.vue'
import FormButton from '@/components/Field/FormButton.vue'
import Button from '@/components/ui/button/Button.vue'
import showDrawer from '@/lib/showDrawer'
import { h } from 'vue'
import TextShareDrawer from '@/components/Drawer/TextShareDrawer.vue'
import { cx } from 'class-variance-authority'
const textStepList = [
{ component: TextUploadInputTextView, key: 'input' },
{ component: ResultIndexView, key: 'result' },
]
const step = ref('input')
const renderComponent = computed(() => {
return textStepList.find((item) => item.key === step.value)?.component
})
const formRef = ref<InstanceType<typeof VeeForm>>()
watch(() => step.value, (newVal) => {
if (newVal === 'input') {
formRef.value?.form?.resetForm()
// formRef.value?.form?.setValues({ file: null })
}
})
</script>
<template>
<VeeForm v-slot="{ setValues, values }">
<div class="rounded-xl p-5 bg-white/50 backdrop-blur-xl w-full lg:w-200 gap-5 flex flex-col">
<div class="text-xl font-normal">输入文本</div>
<div class="relative">
<MarkdownInputField name="text" placeholder="使用我们的文本处理器轻松分享,翻译,总结,生成图片,询问大模型"
class="max-h-[50vh] min-h-40 overflow-y-auto max-w-full [&>*]:pr-10" rules="required" />
<Button variant="ghost" size="icon" :class="cx('absolute right-2 top-2 hover:bg-black/10 transition-all duration-300',
values.text?.length > 0 ? 'opacity-100' : 'opacity-0 pointer-events-none'
)" @click="() => {
setValues({ text: '' })
}">
<LucideX />
</Button>
</div>
<div class="flex flex-row gap-3">
<FormButton @click="async (form) => {
const { text } = form?.values || {}
showDrawer({ render: ({ hide }) => h(TextShareDrawer, { hide, text }) })
}">
<LucideShare class="size-4" />提交
</FormButton>
</div>
<VeeForm ref="formRef" v-slot="{ values }" :keepValues="true">
<div class="rounded-xl p-5 bg-white/50 backdrop-blur-xl w-full lg:w-200">
<component :is="renderComponent" :data="values" @change="(key: string) => {
step = key
}" />
</div>
</VeeForm>
</template>

View File

@@ -0,0 +1,45 @@
<script lang="ts" setup>
import MarkdownInputField from '@/components/Field/MarkdownInputField.vue'
import FormButton from '@/components/Field/FormButton.vue'
import Button from '@/components/ui/button/Button.vue'
import showDrawer from '@/lib/showDrawer'
import { h } from 'vue'
import TextShareDrawer from '@/components/Drawer/TextShareDrawer.vue'
import { cx } from 'class-variance-authority'
const form = useFormContext()
const emit = defineEmits<{
(e: 'change', key: string): void
}>()
const handleTextShare = ({ type, config }: { type: string, config: any }) => {
form?.setFieldValue('handle_type', type)
form?.setFieldValue('config', config)
emit('change', 'result')
}
</script>
<template>
<div class="gap-5 flex flex-col">
<div class="text-xl font-normal">输入文本</div>
<div class="relative">
<MarkdownInputField name="text" placeholder="使用我们的文本处理器轻松分享,翻译,总结,生成图片,询问大模型"
class="max-h-[50vh] min-h-40 overflow-y-auto max-w-full [&>*]:pr-10" rules="required" />
<Button variant="ghost" size="icon" :class="cx('absolute right-2 top-2 hover:bg-black/10 transition-all duration-300',
form?.values.text?.length > 0 ? 'opacity-100' : 'opacity-0 pointer-events-none'
)" @click="() => {
form?.setValues({ text: '' })
}">
<LucideX />
</Button>
</div>
<div class="flex flex-row gap-3">
<FormButton @click="async (form) => {
const { text } = form?.values || {}
showDrawer({ render: ({ hide }) => h(TextShareDrawer, { hide, text, onTextHandle: handleTextShare }) })
}">
<LucideShare class="size-4" />提交
</FormButton>
</div>
</div>
</template>

View File

@@ -0,0 +1,65 @@
<script setup lang="ts">
import SwitchField from '../Field/SwitchField.vue';
import InputField from '../Field/InputField.vue';
import SelectField from '../Field/SelectField.vue';
import FormButton from '../Field/FormButton.vue';
const props = defineProps<{
hide: () => void
text: string
onTextHandle: ({ type, config }: { type: string, config: any }) => void
}>()
</script>
<template>
<VeeForm v-slot="{ values, setFieldValue }" :initialValues="{ download_nums: 1, expire_time: 1440 }">
<div class="flex flex-col gap-3">
<h2 class="text-lg font-bold">分享选项</h2>
<div class="flex flex-row items-center gap-2 text-sm">
<SelectField name="download_nums" label="浏览次数" :options="[
{ label: '1次浏览', value: 1 },
{ label: '2次浏览', value: 2 },
{ label: '3次浏览', value: 3 },
{ label: '5次浏览', value: 4 },
{ label: '10次浏览', value: 5 },
]" />
<SelectField name="expire_time" label="过期时间" :options="[
{ label: '5分钟', value: 5 },
{ label: '1小时', value: 60 },
{ label: '1天', value: 1440 },
{ label: '3天', value: 4320 },
]" />
后过期
</div>
<div class="flex flex-col gap-1">
<div class="flex flex-row gap-3 min-h-9">
<SwitchField name="has_pickup_code" label="取件码" :rules="((value: boolean) => {
if (!!value) {
setFieldValue('has_password', false)
}
return true
})" />
</div>
<div class="flex flex-row gap-3 min-h-9">
<SwitchField name="has_password" label="密码保护" :rules="((value: boolean) => {
if (!!value) {
setFieldValue('has_pickup_code', false)
}
return true
})" />
<InputField v-if="!!values.has_password" name="password" placeholder="请输入密码" rules="required" />
</div>
<div class="flex flex-row gap-3 min-h-9">
<SwitchField name="has_notify" label="已读通知" />
<InputField v-if="!!values.has_notify" name="notify_email" placeholder="请输入邮箱"
rules="required" />
</div>
</div>
<FormButton @click="async (form) => {
onTextHandle({ type: 'text-share', config: values })
hide()
}">提交</FormButton>
</div>
</VeeForm>
</template>

View File

@@ -1,33 +1,41 @@
<script lang="ts" setup>
import FileShareResult from '@/components/Result/FileShareResult.vue'
import TextShareResult from '@/components/Result/TextShareResult.vue'
type basehandleData = { config: any, handle_type: string }
type filehandleData = { file: File, file_id: string } & basehandleData
type texthandleData = { text: string } & basehandleData
const props = defineProps<{
data: filehandleData
data: filehandleData | texthandleData
}>()
const emit = defineEmits<{
(e: 'change', key: string): void
}>()
// console.log(props.data)
const handleList = [
{ component: FileShareResult, key: 'file-share' },
// { component: FileShareResult, key: 'file-share' },
{ component: TextShareResult, key: 'text-share' },
]
const handleComponent = computed(() => {
return handleList.find((item) => item.key === props?.data?.handle_type)?.component
})
// vue这个ts蠢的没边了本来想写component: FileShareResult | TextShareResult结果不行
</script>
<template>
<div class="">
<component :is="handleComponent" :data="data" @change="(key: string) => {
emit('change', key)
}" />
<div>
<FileShareResult
v-if="handleComponent === FileShareResult && 'file' in data"
:data="data"
@change="(key: string) => emit('change', key)"
/>
<TextShareResult
v-else-if="handleComponent === TextShareResult && 'text' in data"
:data="data"
@change="(key: string) => emit('change', key)"
/>
</div>
</template>

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'
import { useClipboard } from '@vueuse/core'
import { toast } from 'vue-sonner'
import { useQuery } from '@tanstack/vue-query';
const props = defineProps<{
data: { text: string, config: any, handle_type: string }
}>()
const emit = defineEmits<{
(e: 'change', key: string): void
}>()
const { data } = useQuery({
queryKey: ['create-share', props?.data?.text],
queryFn: async () => {
const { config, text } = props?.data || {}
const data = await $fetch<{
code: number
data: {
id?: string
}
}>(`/api/share`, {
method: 'POST',
body: {
type: 'text',
config,
data: text,
}
})
return data?.data
}
})
const appConfig = useAppConfig()
const url = computed(() => {
const { id } = data?.value || {}
return `${appConfig?.value?.site_url}/s/${id}`
})
const { copy } = useClipboard()
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex flex-row justify-between">
<h2 class="text-lg">分享成功</h2>
<div class="flex flex-row gap-2 basis-1/2">
<Button variant="outline" class="bg-white/70" size="icon" @click="() => {
emit('change', 'input')
}">
<LucideHome />
</Button>
<Input v-model="url" class="bg-white/70" />
<Button variant="outline" class="bg-white/70" size="icon" @click="() => {
copy(url)
toast.success('复制成功')
}">
<LucideCopy />
</Button>
<Button variant="outline" class="bg-white/70" size="icon">
<LucideQrCode />
</Button>
</div>
</div>
<div class="prose rounded-md bg-white/70 p-3 w-full max-w-full" v-html="props?.data?.text" />
</div>
</template>