mirror of
https://github.com/keven1024/015.git
synced 2026-05-26 07:08:02 +00:00
feat(front): implement text sharing functionality with new TextShareHandle and TextShareResult components
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
45
front/components/Home/Text/TextUploadInputTextView.vue
Normal file
45
front/components/Home/Text/TextUploadInputTextView.vue
Normal 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>
|
||||
65
front/components/Preprocessing/TextShareHandle.vue
Normal file
65
front/components/Preprocessing/TextShareHandle.vue
Normal 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>
|
||||
@@ -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>
|
||||
70
front/components/Result/TextShareResult.vue
Normal file
70
front/components/Result/TextShareResult.vue
Normal 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>
|
||||
Reference in New Issue
Block a user