houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
820397 1 <template>
H 2   <!-- 定义 tab 组件:撰写/回复等 -->
3   <DefineTab v-slot="{ active, text, itemClick }">
4     <span
5       class="inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
6       :class="active ? 'text-black shadow-md' : 'hover:bg-[#DDDFE3]'"
7       @click="itemClick"
8     >
9       {{ text }}
10     </span>
11   </DefineTab>
12   <!-- 定义 label 组件:长度/格式/语气/语言等 -->
13   <DefineLabel v-slot="{ label, hint, hintClick }">
14     <h3 class="mt-5 mb-3 flex items-center justify-between text-[14px]">
15       <span>{{ label }}</span>
16       <span
17         @click="hintClick"
18         v-if="hint"
19         class="flex items-center text-[12px] text-[#846af7] cursor-pointer select-none"
20       >
21         <Icon icon="ep:question-filled" />
22         {{ hint }}
23       </span>
24     </h3>
25   </DefineLabel>
26
27   <div class="flex flex-col" v-bind="$attrs">
28     <!-- tab -->
29     <div class="w-full pt-2 bg-[#f5f7f9] flex justify-center">
30       <div class="w-[303px] rounded-full bg-[#DDDFE3] p-1 z-10">
31         <div
32           class="flex items-center relative after:content-[''] after:block after:bg-white after:h-[30px] after:w-1/2 after:absolute after:top-0 after:left-0 after:transition-transform after:rounded-full"
33           :class="
34             selectedTab === AiWriteTypeEnum.REPLY && 'after:transform after:translate-x-[100%]'
35           "
36         >
37           <ReuseTab
38             v-for="tab in tabs"
39             :key="tab.value"
40             :text="tab.text"
41             :active="tab.value === selectedTab"
42             :itemClick="() => switchTab(tab.value)"
43           />
44         </div>
45       </div>
46     </div>
47     <div
48       class="px-7 pb-2 flex-grow overflow-y-auto lg:block w-[380px] box-border bg-[#f5f7f9] h-full"
49     >
50       <div>
51         <template v-if="selectedTab === 1">
52           <ReuseLabel label="写作内容" hint="示例" :hint-click="() => example('write')" />
53           <el-input
54             type="textarea"
55             :rows="5"
56             :maxlength="500"
57             v-model="formData.prompt"
58             placeholder="请输入写作内容"
59             showWordLimit
60           />
61         </template>
62
63         <template v-else>
64           <ReuseLabel label="原文" hint="示例" :hint-click="() => example('reply')" />
65           <el-input
66             type="textarea"
67             :rows="5"
68             :maxlength="500"
69             v-model="formData.originalContent"
70             placeholder="请输入原文"
71             showWordLimit
72           />
73
74           <ReuseLabel label="回复内容" />
75           <el-input
76             type="textarea"
77             :rows="5"
78             :maxlength="500"
79             v-model="formData.prompt"
80             placeholder="请输入回复内容"
81             showWordLimit
82           />
83         </template>
84
85         <ReuseLabel label="长度" />
86         <Tag v-model="formData.length" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LENGTH)" />
87         <ReuseLabel label="格式" />
88         <Tag v-model="formData.format" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_FORMAT)" />
89         <ReuseLabel label="语气" />
90         <Tag v-model="formData.tone" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_TONE)" />
91         <ReuseLabel label="语言" />
92         <Tag v-model="formData.language" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LANGUAGE)" />
93
94         <div class="flex items-center justify-center mt-3">
95           <el-button :disabled="isWriting" @click="reset">重置</el-button>
96           <el-button :loading="isWriting" @click="submit" color="#846af7">生成</el-button>
97         </div>
98       </div>
99     </div>
100   </div>
101 </template>
102
103 <script setup lang="ts">
104 import { createReusableTemplate } from '@vueuse/core'
105 import { ref } from 'vue'
106 import Tag from './Tag.vue'
107 import { WriteVO } from 'src/api/ai/write'
108 import { omit } from 'lodash-es'
109 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
110 import { AiWriteTypeEnum, WriteExample } from '@/views/ai/utils/constants'
111
112 type TabType = WriteVO['type']
113
114 const message = useMessage() // 消息弹窗
115
116 defineProps<{
117   isWriting: boolean
118 }>()
119
120 const emits = defineEmits<{
121   (e: 'submit', params: Partial<WriteVO>)
122   (e: 'example', param: 'write' | 'reply')
123   (e: 'reset')
124 }>()
125
126 /** 点击示例的时候,将定义好的文章作为示例展示出来 **/
127 const example = (type: 'write' | 'reply') => {
128   formData.value = {
129     ...initData,
130     ...omit(WriteExample[type], ['data'])
131   }
132   emits('example', type)
133 }
134
135 /** 重置,将表单值作为初选值 **/
136 const reset = () => {
137   formData.value = { ...initData }
138   emits('reset')
139 }
140
141 const selectedTab = ref<TabType>(AiWriteTypeEnum.WRITING)
142 const tabs: {
143   text: string
144   value: TabType
145 }[] = [
146   { text: '撰写', value: AiWriteTypeEnum.WRITING },
147   { text: '回复', value: AiWriteTypeEnum.REPLY }
148 ]
149 const [DefineTab, ReuseTab] = createReusableTemplate<{
150   active?: boolean
151   text: string
152   itemClick: () => void
153 }>()
154
155 /**
156  * 可以在 template 里边定义可复用的组件,DefineLabel,ReuseLabel 是采用的解构赋值,都是 Vue 组件
157  *
158  * 直接通过组件的形式使用,<DefineLabel v-slot="{ label, hint, hintClick }"> 中间是需要复用的组件代码 <DefineLabel />,通过 <ReuseLabel /> 来使用定义的组件
159  * DefineLabel 里边的 v-slot="{ label, hint, hintClick }"相当于是解构了组件的 prop,需要注意的是 boolean 类型,需要显式的赋值比如 <ReuseLabel :flag="true" />
160  * 事件也得以 prop 形式传入,不能是 @event的形式,比如下面的 hintClick 需要<ReuseLabel :hintClick="() => { doSomething }"/>
161  *
162  * @see https://vueuse.org/createReusableTemplate
163  */
164 const [DefineLabel, ReuseLabel] = createReusableTemplate<{
165   label: string
166   class?: string
167   hint?: string
168   hintClick?: () => void
169 }>()
170
171 const initData: WriteVO = {
172   type: 1,
173   prompt: '',
174   originalContent: '',
175   tone: 1,
176   language: 1,
177   length: 1,
178   format: 1
179 }
180 const formData = ref<WriteVO>({ ...initData })
181
182 /** 用来记录切换之前所填写的数据,切换的时候给赋值回来 **/
183 const recordFormData = {} as Record<AiWriteTypeEnum, WriteVO>
184
185 /** 切换tab **/
186 const switchTab = (value: TabType) => {
187   if (value !== selectedTab.value) {
188     // 保存之前的久数据
189     recordFormData[selectedTab.value] = formData.value
190     selectedTab.value = value
191     // 将之前的旧数据赋值回来
192     formData.value = { ...initData, ...recordFormData[value] }
193   }
194 }
195
196 /** 提交写作 */
197 const submit = () => {
198   if (selectedTab.value === 2 && !formData.value.originalContent) {
199     message.warning('请输入原文')
200     return
201   }
202   if (!formData.value.prompt) {
203     message.warning(`请输入${selectedTab.value === 1 ? '写作' : '回复'}内容`)
204     return
205   }
206   emits('submit', {
207     /** 撰写的时候没有 originalContent 字段**/
208     ...(selectedTab.value === 1 ? omit(formData.value, ['originalContent']) : formData.value),
209     /** 使用选中 tab 值覆盖当前的 type 类型 **/
210     type: selectedTab.value
211   })
212 }
213 </script>