WorkflowAdd.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <div class="workflow-add container-height">
  3. <Breadcrumb />
  4. <div class="flex-between mt10">
  5. <div class="flex_1 mr20">
  6. <el-form :model="ruleForm" :rules="rules" ref="ruleFormRef" label-position="top" class="page-add">
  7. <div class="padding16 bg_color_fff border_radius_10 box_shadow_card" v-show="!isFullscreen">
  8. <div class="gap10">
  9. <div class="line_vertical"></div>
  10. <div class="font_size20 bold"><span class="color_required font_size16">*</span>{{$t('workflowTrade.fileUpload')}}</div>
  11. <div class="gray999 font_size14">{{$t('workflowTrade.supportFormat')}}:JSON、YAML、ZIP({{$t('common.maxSize')}}10MB)</div>
  12. </div>
  13. <div class="mt10">
  14. <el-form-item label="" prop="workflowFile">
  15. <!-- 上传 -->
  16. <FileUploader
  17. ref="fileUploader"
  18. accept=".json,.yaml,.yml,.zip"
  19. :multiple="false"
  20. :limit="1"
  21. :auto-upload="true"
  22. :drag="true"
  23. v-model="files"
  24. :tip="$t('workflowTrade.workflowFileUploadTip')"
  25. />
  26. </el-form-item>
  27. </div>
  28. </div>
  29. <div class="padding16 bg_color_fff border_radius_10 mt10 box_shadow_card">
  30. <div class="gap10">
  31. <div class="line_vertical"></div>
  32. <div class="font_size20 bold">{{$t('common.basicInfo')}}</div>
  33. </div>
  34. <div class="mt10">
  35. <el-form-item :label="$t('workflowTrade.workflowTitle')" prop="workflowTitle" v-show="!isFullscreen">
  36. <el-input v-model="ruleForm.workflowTitle" :placeholder="$t('workflowTrade.placeholderWorkflowTitle')" maxlength="50" show-word-limit/>
  37. </el-form-item>
  38. <el-form-item :label="$t('workflowTrade.workflowCategory')" prop="categoryId3" v-show="!isFullscreen">
  39. <el-cascader
  40. v-model="categoryIdList"
  41. :options="categoryListTree"
  42. :placeholder="$t('workflowTrade.placeholderWorkflowCategory')"
  43. style="width:100%"
  44. :props="{
  45. label: 'categoryName',
  46. value: 'categoryId',
  47. children: 'children',
  48. }"
  49. />
  50. </el-form-item>
  51. <el-form-item :label="$t('workflowTrade.workflowDescription')" prop="description" v-show="!isFullscreen">
  52. <el-input type="textarea"
  53. v-model="ruleForm.description"
  54. :autosize="{ minRows: 5 }"
  55. :placeholder="$t('workflowTrade.placeholderWorkflowDescription')"
  56. maxlength="500" show-word-limit/>
  57. </el-form-item>
  58. <el-form-item :label="$t('common.coverImage')" prop="coverImage" v-show="!isFullscreen">
  59. <!-- 图片类型 -->
  60. <FileUploader
  61. ref="fileUploader"
  62. accept="image/*"
  63. :multiple="false"
  64. :limit="1"
  65. :auto-upload="true"
  66. list-type="picture-card"
  67. :data="{ directory: 'workflow' }"
  68. buttonText=""
  69. v-model="coverImage"
  70. tip=""
  71. @success="handleUploadSuccess"
  72. @error="handleUploadError"
  73. @progress="handleUploadProgress"
  74. />
  75. </el-form-item>
  76. <el-form-item :label="$t('common.workflowPreview')" v-show="!isFullscreen">
  77. <div>{{$t('workflowTrade.supportBatchUpload')}}</div>
  78. <!-- 图片类型 -->
  79. <FileUploader
  80. ref="fileUploader"
  81. accept="image/*"
  82. :multiple="true"
  83. :limit="5"
  84. :auto-upload="true"
  85. list-type="picture-card"
  86. :data="{ directory: 'workflow' }"
  87. buttonText=""
  88. v-model="images"
  89. tip=""
  90. @success="handleUploadSuccess"
  91. @error="handleUploadError"
  92. @progress="handleUploadProgress"
  93. />
  94. </el-form-item>
  95. <!-- <el-form-item :label="$t('common.Detail')" prop="workflowContent">
  96. <BlockNoteEditor v-model="editorContent" :editable="true" class="border"/>
  97. </el-form-item> -->
  98. <el-form-item :label="$t('common.Detail')" prop="workflowContent">
  99. <div class="editor-container" :class="{ 'fullscreen': isFullscreen }">
  100. <div class="editor-header" v-if="isFullscreen">
  101. <span class="editor-title">{{$t('common.Detail')}}</span>
  102. <el-button
  103. icon="FullScreen"
  104. @click="toggleFullscreen"
  105. size="small"
  106. class="exit-fullscreen-btn"
  107. >
  108. {{$t('common.exitFullscreen')}}
  109. </el-button>
  110. </div>
  111. <div class="editor-content">
  112. <BlockNoteEditor
  113. v-model="editorContent"
  114. :editable="true"
  115. class="border"
  116. :class="{ 'fullscreen-editor': isFullscreen }"
  117. />
  118. </div>
  119. <el-button
  120. v-if="!isFullscreen"
  121. icon="FullScreen"
  122. @click="toggleFullscreen"
  123. size="small"
  124. class="fullscreen-btn"
  125. >
  126. {{$t('common.fullscreenEdit')}}
  127. </el-button>
  128. </div>
  129. </el-form-item>
  130. </div>
  131. </div>
  132. <div class="padding16 bg_color_fff border_radius_10 mt10 box_shadow_card" v-show="!isFullscreen">
  133. <div class="gap10 mb20">
  134. <div class="line_vertical"></div>
  135. <div class="font_size20 bold">{{$t('workflowTrade.priceSetting')}}</div>
  136. </div>
  137. <el-form-item :label="$t('workflowTrade.paySetting')" style="width: 600px">
  138. <div class="gap20 mt10 mb20">
  139. <div class="payType gap10" @click="workflowPriceType = 'pay'"
  140. :class="{'active': workflowPriceType === 'pay'}">
  141. <div class="checkType"></div>
  142. <div>{{$t('workflowTrade.paySettingTip')}}</div>
  143. </div>
  144. <div class="payType gap10" @click="workflowPriceType = 'free';ruleForm.workflowPrice=''"
  145. :class="{'active': workflowPriceType === 'free'}">
  146. <div class="checkType"></div>
  147. <div>{{$t('workflowTrade.freeSettingTip')}}</div>
  148. </div>
  149. </div>
  150. <el-input v-model="ruleForm.workflowPrice" :placeholder="$t('workflowTrade.placeholderPrice')" maxlength="50" type="number" v-if="workflowPriceType === 'pay'">
  151. <template #append>{{$t('common.baomibi')}}</template>
  152. </el-input>
  153. </el-form-item>
  154. </div>
  155. <div class="mt20" v-show="!isFullscreen">
  156. <el-button type="primary" class="font_size16 gradient"
  157. :loading="isSubmiting"
  158. @click="submitForm" size="large">
  159. <el-icon><Promotion /></el-icon>
  160. <span class="ml10">{{$t('common.publishWorkflow')}}</span>
  161. </el-button>
  162. <el-button class="font_size16" @click="goBack" size="large">
  163. <el-icon><Close /></el-icon>
  164. <span class="ml10">{{$t('common.cancel')}}</span>
  165. </el-button>
  166. </div>
  167. </el-form>
  168. </div>
  169. <!-- 右边内容 -->
  170. <div class="detail_right">
  171. <div class="detail_right_content detail_right">
  172. <div class="padding16 bg_color_fff border_radius_16 box_shadow_card">
  173. <div class="gap10">
  174. <div class="line_vertical"></div>
  175. <div class="font_size20 bold">{{$t('workflowTradeAdd.publishRules')}}</div>
  176. </div>
  177. <!-- 无序列表 -->
  178. <div class="font_size16 ql-container">
  179. <div class="ql-editor">
  180. <div v-html="release_rules"></div>
  181. </div>
  182. </div>
  183. </div>
  184. </div>
  185. </div>
  186. </div>
  187. </div>
  188. </template>
  189. <script setup>
  190. import { ref, onMounted, reactive, watchEffect, nextTick } from 'vue'
  191. import { useRoute, useRouter } from 'vue-router'
  192. import FileUploader from '@/components/FileUploader.vue'
  193. import DGTMessage from '@/utils/message'
  194. import BlockNoteEditor from '@/components/BlockNoteEditor.vue';
  195. const route = useRoute()
  196. const router = useRouter()
  197. import { getCategoryListTree } from '@/api/category.js'
  198. import { getAgreementType } from '@/api/common.js'
  199. import { publishAdd, getPublishDetail,publishEdit } from '@/api/publish.js'
  200. import { useI18n } from 'vue-i18n'
  201. const { t } = useI18n()
  202. // 防止重复提交的加载状态
  203. const isSubmiting = ref(false)
  204. // 发布规则
  205. const release_rules = ref('');
  206. // 从路由参数中获取 activePlatform
  207. const activePlatform = ref(route.query.activePlatform || '');
  208. // 从路由参数中获取 publishId
  209. const publishId = ref(route.query.id || '');
  210. // 图片
  211. const images = ref([]);
  212. const coverImage = ref([]);
  213. //附件
  214. const files = ref([]);
  215. // 分类列表树
  216. const categoryListTree = ref([]);
  217. // 选择的分类id列表
  218. const categoryIdList = ref([])
  219. // 编辑器内容
  220. const editorContent = ref(null);
  221. const workflowPriceType = ref('pay'); // 价格类型,默认免费
  222. // 表单实例
  223. const ruleFormRef = ref(null)
  224. const ruleForm = reactive({
  225. publishId: '',
  226. workflowTitle: '',
  227. categoryId1: '',
  228. categoryId2: '',
  229. categoryId3: '',
  230. workflowFile: '',
  231. workflowPrice: '',
  232. description: '',
  233. previewImage: '',
  234. coverImage: '',
  235. workflowContent: '',
  236. })
  237. watchEffect(() => {
  238. // 将选择的分类id列表赋值给表单的categoryId1、categoryId2、categoryId3
  239. ruleForm.categoryId1 = categoryIdList.value[0] || ''
  240. ruleForm.categoryId2 = categoryIdList.value[1] || ''
  241. ruleForm.categoryId3 = categoryIdList.value[2] || ''
  242. // 将编辑器内容赋值给表单的workflowContent
  243. ruleForm.workflowContent = JSON.stringify(editorContent.value);
  244. ruleForm.coverImage = coverImage.value.map(img => img.url).join(',');
  245. ruleForm.workflowFile = files.value[0]?.url || '';
  246. ruleForm.previewImage = images.value.map(img => img.url).join(',');
  247. })
  248. // 校验规则
  249. const rules = reactive({
  250. workflowFile: [
  251. { required: true, message: t('workflowTrade.placeholderWorkflowFileUpload'), trigger: 'change' },
  252. ],
  253. workflowTitle: [
  254. { required: true, message: t('workflowTrade.placeholderWorkflowTitle'), trigger: 'blur' },
  255. ],
  256. categoryId3: [
  257. { required: true, message: t('workflowTrade.placeholderWorkflowCategory'), trigger: 'blur' },
  258. ],
  259. description: [
  260. { required: true, message: t('workflowTrade.placeholderWorkflowDescription'), trigger: 'blur' },
  261. ],
  262. coverImage: [
  263. { required: true, message: t('workflowTrade.placeholderCoverImageUpload'), trigger: 'change' },
  264. ],
  265. workflowContent: [
  266. { required: true, message: t('workflowTrade.placeholderWorkflowContent'), trigger: 'change' },
  267. ],
  268. })
  269. // 在现有代码后添加键盘事件处理
  270. const handleKeyDown = (e) => {
  271. // ESC键退出全屏
  272. if (e.key === 'Escape' && isFullscreen.value) {
  273. toggleFullscreen();
  274. }
  275. };
  276. onMounted(() => {
  277. // 添加键盘事件监听
  278. window.addEventListener('keydown', handleKeyDown);
  279. getCategoryListTreeFn();
  280. if(publishId.value){
  281. getDetail();
  282. }
  283. getAgreementTypeFn();
  284. });
  285. // 提交表单
  286. const submitForm = async () => {
  287. // if(!ruleForm.workflowFile){
  288. // DGTMessage.warning('请上传工作流文件')
  289. // return
  290. // }
  291. if(!editorContent.value || editorContent.value.length === 0){
  292. DGTMessage.warning(t('workflowTrade.placeholderWorkflowContent'))
  293. return
  294. }
  295. if(workflowPriceType.value === 'pay'){
  296. if(!ruleForm.workflowPrice){
  297. DGTMessage.warning(t('workflowTrade.placeholderPrice'))
  298. return
  299. }
  300. }
  301. console.log('工作流详情内容',ruleForm)
  302. await ruleFormRef.value.validate((valid, fields) => {
  303. console.log(valid, fields)
  304. if (!valid) {
  305. //报错第一个key
  306. let firstKey = Object.keys(fields)[0]
  307. DGTMessage.warning(fields[firstKey][0].message)
  308. return
  309. }
  310. let req = publishAdd;
  311. if(publishId.value){
  312. req = publishEdit;
  313. }
  314. // 设置提交状态为true,禁用按钮
  315. isSubmiting.value = true
  316. req(ruleForm).then(res => {
  317. console.log(res)
  318. if(res.code === 200){
  319. DGTMessage.success(t('workflowTrade.publishSuccess'))
  320. setTimeout(() => {
  321. goBack();
  322. }, 2000);
  323. }
  324. }).finally(() => {
  325. // 提交完成后,将提交状态设置为false,启用按钮
  326. setTimeout(() => {
  327. isSubmiting.value = false
  328. }, 1000)
  329. })
  330. })
  331. }
  332. // 上传成功处理
  333. const handleUploadSuccess = (response, rawFile, uploadedFiles) => {
  334. // DGTMessage.success('文件上传成功')
  335. console.log('上传成功:', response,uploadedFiles)
  336. }
  337. // 上传失败处理
  338. const handleUploadError = (error, rawFile, uploadedFiles) => {
  339. // DGTMessage.error('文件上传失败')
  340. console.error('上传失败:', error)
  341. }
  342. // 上传进度处理
  343. const handleUploadProgress = (event, file, uploadedFiles) => {
  344. console.log('上传进度:', event.percent)
  345. };
  346. const goBack = () => {
  347. router.back()
  348. };
  349. const getCategoryListTreeFn = () => {
  350. getCategoryListTree().then(res => {
  351. console.log(res)
  352. categoryListTree.value = res.rows || [];
  353. })
  354. };
  355. const getDetail = () => {
  356. getPublishDetail({id:publishId.value}).then(res => {
  357. const detail = res.data || {};
  358. for(let key in ruleForm){
  359. ruleForm[key] = detail[key];
  360. }
  361. nextTick(() => {
  362. if(ruleForm.workflowFile){
  363. files.value = [{url: ruleForm.workflowFile}];
  364. };
  365. if(ruleForm.coverImage){
  366. coverImage.value = ruleForm.coverImage.split(',').map(item => ({url: item}));
  367. };
  368. if(ruleForm.previewImage){
  369. images.value = ruleForm.previewImage.split(',').map(item => ({url: item}));
  370. };
  371. if(ruleForm.categoryId1){
  372. categoryIdList.value = [ruleForm.categoryId1, ruleForm.categoryId2, ruleForm.categoryId3];
  373. };
  374. setTimeout(() => {
  375. if(detail.workflowContent){
  376. editorContent.value = JSON.parse(detail.workflowContent);
  377. }
  378. console.log('editorContent.value',editorContent.value,ruleForm)
  379. }, 500);
  380. });
  381. })
  382. };
  383. // 在 script setup 中添加
  384. const isFullscreen = ref(false);
  385. // 切换全屏的方法
  386. const toggleFullscreen = () => {
  387. isFullscreen.value = !isFullscreen.value;
  388. if (isFullscreen.value) {
  389. // 进入全屏时可能需要调整编辑器焦点
  390. nextTick(() => {
  391. // 如果 BlockNoteEditor 有 focus 方法,可以调用
  392. });
  393. }
  394. };
  395. const getAgreementTypeFn = () => {
  396. getAgreementType({agreementType: 'workflow_rules'}).then(res => {
  397. console.log(res)
  398. release_rules.value = res.data.content || '';
  399. })
  400. };
  401. </script>
  402. <style lang="scss">
  403. .workflow-add{
  404. .payType{
  405. background: #EAF0FF;
  406. border-radius: 8px 8px 8px 8px;
  407. border: 1px solid transparent;
  408. padding: 10px 16px;
  409. &.active{
  410. background: #EAF0FF;
  411. border-color: $primary-color;
  412. .checkType{
  413. border-color: $primary-color;
  414. }
  415. }
  416. .checkType{
  417. width: 14px;
  418. height: 14px;
  419. border:4px solid transparent;
  420. border-radius: 50%;
  421. background-color: #ffffff;
  422. }
  423. }
  424. }
  425. </style>
  426. <style lang="scss">
  427. .editor-container {
  428. position: relative;
  429. width: 100%;
  430. .editor-header {
  431. display: flex;
  432. justify-content: space-between;
  433. align-items: center;
  434. padding: 10px 15px;
  435. background: #f5f5f5;
  436. border-bottom: 1px solid #dcdfe6;
  437. .editor-title {
  438. font-weight: bold;
  439. color: #303133;
  440. }
  441. .exit-fullscreen-btn {
  442. margin-left: auto;
  443. }
  444. }
  445. .fullscreen-btn {
  446. position: absolute;
  447. right: 10px;
  448. top: 10px;
  449. z-index: 10;
  450. }
  451. // .exit-fullscreen-btn{
  452. // }
  453. .fullscreen-btn {
  454. bottom: 10px;
  455. top: auto;
  456. }
  457. &.fullscreen {
  458. position: fixed;
  459. top: 0;
  460. left: 0;
  461. width: 100%;
  462. height: 100vh;
  463. background: white;
  464. z-index: 9999;
  465. padding: 20px;
  466. .blocknote-editor {
  467. height: calc(100vh - 100px) !important; // 减去头部和按钮的高度
  468. }
  469. .editor-content {
  470. height: calc(100vh - 100px);
  471. }
  472. }
  473. }
  474. </style>