WorkflowAdd.vue 16 KB

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