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