index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <div>
  3. <div class="right-top">
  4. <div class="flex align-middle px-[8px]">
  5. <Lang/>
  6. </div>
  7. <div>
  8. <!-- <el-button text>分享</el-button>-->
  9. <el-button type="primary" @click="exportFile">下载</el-button>
  10. <el-button type="success" @click="onSaveTemplate">保存</el-button>
  11. <!-- <el-button text href="https://github.com/dromara/sd-designer" tag="a" target="_blank" rel="noopener noreferrer">-->
  12. <!-- &lt;!&ndash; <a href="https://github.com/dromara/sd-designer" target="_blank" rel="noopener noreferrer"> &ndash;&gt;-->
  13. <!-- &lt;!&ndash; <el-tooltip placement="top" :hide-after="0" :content="t('message.github')"> &ndash;&gt;-->
  14. <!-- <IconGithub class="footer-button"></IconGithub>-->
  15. <!-- &lt;!&ndash; </el-tooltip> &ndash;&gt;-->
  16. <!-- &lt;!&ndash; </a> &ndash;&gt;-->
  17. <!-- </el-button>-->
  18. </div>
  19. </div>
  20. <div class="right-bottom">
  21. <div class="right-tabs">
  22. <div
  23. class="tab"
  24. :class="[ tab.value === rightState && currentTabs.length > 1 ? 'active' : 'no-active' ]"
  25. v-for="tab in currentTabs"
  26. :key="tab.value"
  27. @click="setRightState(tab.value)"
  28. >
  29. {{ tab.label }}
  30. </div>
  31. </div>
  32. <div class="right-content">
  33. <component :is="currentPanelComponent"></component>
  34. </div>
  35. </div>
  36. <FileExport v-model:visible="exportFileDialog" @close="exportFileHide" @save="exportFileHandle"/>
  37. <el-dialog
  38. v-model="showSaveDlg"
  39. title="保存模板"
  40. width="580px"
  41. @close="closeSaveDlg">
  42. <el-form
  43. ref="formRef"
  44. :model="formData"
  45. :rules="rules"
  46. label-width="120px"
  47. >
  48. <el-row>
  49. <el-col :span="24">
  50. <el-form-item prop="templateName" label="模板名称">
  51. <el-input v-model.trim="formData.templateName"/>
  52. </el-form-item>
  53. </el-col>
  54. </el-row>
  55. <el-row>
  56. <el-col :span="24">
  57. <el-form-item prop="productIdentities" label="产品ID">
  58. <el-tag
  59. v-for="tag in productIdentities"
  60. :key="tag"
  61. closable
  62. :disable-transitions="false"
  63. @close="handleDeleteProductId(tag)"
  64. >
  65. {{ tag }}
  66. </el-tag>
  67. <el-input
  68. v-if="productInputVisible"
  69. ref="productInputRef"
  70. v-model.trim="productInputValue"
  71. style="width: 80px !important;"
  72. size="small"
  73. @keyup.enter="handleProductInputConfirm"
  74. @blur="handleProductInputConfirm"
  75. />
  76. <el-button v-else size="small" @click="showProductInput">
  77. + 产品ID
  78. </el-button>
  79. </el-form-item>
  80. </el-col>
  81. </el-row>
  82. </el-form>
  83. <template #footer>
  84. <div class="dialog-footer">
  85. <el-button type="primary" @click="handleSubmit"
  86. >确 定
  87. </el-button>
  88. <el-button @click="showSaveDlg = false">取 消</el-button>
  89. </div>
  90. </template>
  91. </el-dialog>
  92. </div>
  93. </template>
  94. <script lang="ts" setup>
  95. import {computed, watch} from "vue";
  96. import {RightStates, ElementNames} from "@/types/elements";
  97. import {storeToRefs} from "pinia";
  98. import {useMainStore} from "@/store/modules/main";
  99. import Lang from "@/components/Lang/index.vue";
  100. import CanvasStylePanel from "./CanvasStylePanel/index.vue";
  101. import ElemnetStylePanel from "./ElementStylePanel/index.vue";
  102. import EffectStylePanel from "./EffectStylePanel/index.vue";
  103. import LayerStylePanel from "./LayerStylePanel/index.vue";
  104. import useI18n from "@/hooks/useI18n";
  105. import {useTemplatesStore} from "@/store";
  106. import useCanvasExport from "@/hooks/useCanvasExport";
  107. import {updateDesignTemplate} from "@/api/template";
  108. import {ElMessage, FormInstance} from "element-plus";
  109. import {getToken} from "@/utils/js-cookie";
  110. import useCanvas from "@/views/Canvas/useCanvas";
  111. const templatesStore = useTemplatesStore()
  112. const {getJSONData} = useCanvasExport()
  113. const {t} = useI18n();
  114. const mainStore = useMainStore();
  115. const {canvasObject, rightState} = storeToRefs(mainStore);
  116. const exportFileDialog = ref(false)
  117. const showSaveDlg = ref(false)
  118. const formRef = ref<FormInstance>();
  119. const productIdentities = ref([])
  120. const productInputVisible = ref(false)
  121. const productInputRef = ref()
  122. const productInputValue = ref("")
  123. const rules = reactive({
  124. templateName: [{required: true, message: "请输入名称", trigger: "blur"}],
  125. });
  126. const formData = reactive({
  127. templateName: '',
  128. productIdentity: '',
  129. jsonContent: '',
  130. id: 0,
  131. });
  132. const showProductInput = () => {
  133. productInputVisible.value = true
  134. nextTick(() => {
  135. productInputRef.value!.input!.focus()
  136. })
  137. }
  138. const handleDeleteProductId = (tag) => {
  139. productIdentities.value.splice(productIdentities.value.indexOf(tag), 1)
  140. }
  141. const handleProductInputConfirm = () => {
  142. if (productInputValue.value) {
  143. productIdentities.value.push(productInputValue.value)
  144. }
  145. productInputVisible.value = false
  146. productInputValue.value = ''
  147. }
  148. const exportFileHide = () => {
  149. exportFileDialog.value = false
  150. }
  151. const exportFileHandle = () => {
  152. exportFileDialog.value = false
  153. }
  154. const exportFile = () => {
  155. exportFileDialog.value = true
  156. }
  157. const onSaveTemplate = () => {
  158. if (!getToken('token')) {
  159. ElMessage.error('当前用户未登录,设计无法保存')
  160. return
  161. }
  162. //检查一下是否每个元素都给了itemName
  163. const [canvas] = useCanvas()
  164. const objects = canvas.getObjects().filter((object) => !["WorkSpaceMaskType", "WorkSpaceClipType", "WorkSpaceSafeType", "WorkSpaceClipType"].includes(object.id));
  165. if (objects.findIndex((object) => !object.itemName) >= 0) {
  166. ElMessage.error('部分元素未设置名称,请设置元素名称后保存')
  167. return
  168. }
  169. const groupByItemName = objects.reduce((acc, item) => {
  170. if (!acc[item.itemName]) {
  171. acc[item.itemName] = [];
  172. }
  173. acc[item.itemName].push(item);
  174. return acc;
  175. }, {});
  176. if (Object.keys(groupByItemName).length < objects.length) {
  177. ElMessage.error('部分元素名称重复,请保证各个元素名称唯一')
  178. return
  179. }
  180. formData.templateName = templatesStore.templateName
  181. productIdentities.value = templatesStore.productIdentity
  182. showSaveDlg.value = true
  183. }
  184. const handleSubmit = () => {
  185. formRef.value.validate((valid: any) => {
  186. if (valid) {
  187. formData.id = templatesStore.templateId
  188. formData.productIdentity = productIdentities.value ? productIdentities.value.join(",") : ""
  189. formData.jsonContent = getJSONData()
  190. updateDesignTemplate(JSON.stringify(formData)).then((res) => {
  191. if (res.httpCode == 200) {
  192. ElMessage.success('保存成功')
  193. showSaveDlg.value = false
  194. }
  195. })
  196. }
  197. })
  198. }
  199. const closeSaveDlg = () => {
  200. formData.id = 0
  201. formData.templateName = templatesStore.templateName
  202. formData.jsonContent = ''
  203. }
  204. const canvasTabs = [
  205. {label: t("style.canvas"), value: RightStates.ELEMENT_CANVAS},
  206. // { label: t("style.layer"), value: RightStates.ELEMENT_LAYER },
  207. ];
  208. const styleTabs = [
  209. {label: t("style.style"), value: RightStates.ELEMENT_STYLE},
  210. // { label: t("style.layer"), value: RightStates.ELEMENT_LAYER },
  211. ];
  212. const setRightState = (value: RightStates) => {
  213. mainStore.setRightState(value);
  214. };
  215. const currentTabs = computed(() => {
  216. if (!canvasObject.value) return canvasTabs;
  217. if (canvasObject.value.type.toLowerCase() === ElementNames.REFERENCELINE) return canvasTabs;
  218. return styleTabs;
  219. });
  220. watch(currentTabs, () => {
  221. const currentTabsValue: RightStates[] = currentTabs.value.map(
  222. (tab) => tab.value
  223. );
  224. if (!currentTabsValue.includes(rightState.value)) {
  225. mainStore.setRightState(currentTabsValue[0]);
  226. }
  227. });
  228. const currentPanelComponent = computed(() => {
  229. const panelMap = {
  230. [RightStates.ELEMENT_CANVAS]: CanvasStylePanel,
  231. [RightStates.ELEMENT_STYLE]: ElemnetStylePanel,
  232. [RightStates.ELEMENT_EFFECT]: EffectStylePanel,
  233. [RightStates.ELEMENT_LAYER]: LayerStylePanel,
  234. };
  235. return panelMap[rightState.value as RightStates.ELEMENT_STYLE];
  236. });
  237. </script>
  238. <style lang="scss" scoped>
  239. .right-top {
  240. height: 40px;
  241. width: 100%;
  242. display: flex;
  243. align-items: center;
  244. border-bottom: 1px solid $borderColor;
  245. }
  246. .right-bottom {
  247. height: calc(100% - 40px);
  248. }
  249. .right-tabs {
  250. height: 32px;
  251. font-size: 12px;
  252. flex-shrink: 0;
  253. display: flex;
  254. user-select: none;
  255. }
  256. .tab {
  257. flex: 1;
  258. display: flex;
  259. justify-content: center;
  260. align-items: center;
  261. background-color: $lightGray;
  262. border-bottom: 1px solid $borderColor;
  263. cursor: pointer;
  264. &.active {
  265. background-color: #fff;
  266. border-bottom-color: #fff;
  267. }
  268. & + .tab {
  269. border-left: 1px solid $borderColor;
  270. }
  271. }
  272. .right-content {
  273. padding: 10px 5px 10px 10px;
  274. font-size: 13px;
  275. overflow-y: scroll;
  276. overflow-x: hidden;
  277. height: 100%;
  278. // @include overflow-overlay();
  279. }
  280. </style>