24282 před 11 měsíci
rodič
revize
2f534eaf8c
24 změnil soubory, kde provedl 242 přidání a 60 odebrání
  1. 4 0
      jy-business/pom.xml
  2. 6 1
      jy-business/src/main/java/com/jy/business/payment/model/dto/PaymentRequestsDto.java
  3. 1 1
      jy-business/src/main/java/com/jy/business/payment/model/entity/PaymentRequests.java
  4. 2 2
      jy-business/src/main/java/com/jy/business/payment/model/entity/PaymentRequestsDetail.java
  5. 1 1
      jy-business/src/main/java/com/jy/business/payment/model/table/PaymentRequestsDetailTable.java
  6. 25 1
      jy-business/src/main/java/com/jy/business/payment/service/impl/PaymentRequestsServiceImpl.java
  7. 0 10
      jy-flow/pom.xml
  8. 11 0
      jy-framework/pom.xml
  9. 10 0
      jy-framework/src/main/java/com/jy/framework/exception/UnifiedExceptionHandler.java
  10. 7 0
      jy-framework/src/main/java/com/jy/framework/model/constants/FlowConstant.java
  11. 1 0
      jy-system/src/main/java/com/jy/system/dao/SysMenuDao.java
  12. 5 0
      jy-ui/src/api/flow/definition.ts
  13. 5 0
      jy-ui/src/api/flow/execute.ts
  14. 9 3
      jy-ui/src/components/ADialog/index.vue
  15. 3 3
      jy-ui/src/components/AForm/ADatePicker.vue
  16. 1 1
      jy-ui/src/components/AForm/AInput.vue
  17. 1 1
      jy-ui/src/components/AForm/AInputNumber.vue
  18. 1 1
      jy-ui/src/components/AForm/ASelect.vue
  19. 1 1
      jy-ui/src/components/FlieUpload/index.vue
  20. 36 11
      jy-ui/src/views/business/payment/requests/flowDetail.vue
  21. 18 2
      jy-ui/src/views/business/payment/requests/index.vue
  22. 4 1
      jy-ui/src/views/components/DeptTreeSelect/index.vue
  23. 1 1
      jy-ui/src/views/flow/definition/index.vue
  24. 89 19
      jy-ui/src/views/flow/taskTodo/index.vue

+ 4 - 0
jy-business/pom.xml

@@ -22,6 +22,10 @@
             <groupId>com.jy</groupId>
             <artifactId>jy-system</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.jy</groupId>
+            <artifactId>jy-flow</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 6 - 1
jy-business/src/main/java/com/jy/business/payment/model/dto/PaymentRequestsDto.java

@@ -1,17 +1,22 @@
 package com.jy.business.payment.model.dto;
 
 import com.jy.business.payment.model.entity.PaymentRequests;
+import com.jy.business.payment.model.entity.PaymentRequestsDetail;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.util.List;
+
 /**
  * 请款新增编辑入参实体
  *
- * @author 
+ * @author
  * @since 2024-10-18
  */
 @Getter
 @Setter
 public class PaymentRequestsDto extends PaymentRequests {
 
+    private List<PaymentRequestsDetail> paymentRequestsDetailList;
+
 }

+ 1 - 1
jy-business/src/main/java/com/jy/business/payment/model/entity/PaymentRequests.java

@@ -132,6 +132,6 @@ public class PaymentRequests extends BaseIdPo {
      */
     @TableLogic
     @TableField(fill = FieldFill.INSERT)
-    private Long delFlag;
+    private String delFlag;
 
 }

+ 2 - 2
jy-business/src/main/java/com/jy/business/payment/model/entity/PaymentRequestsDetail.java

@@ -26,7 +26,7 @@ public class PaymentRequestsDetail extends BaseIdPo {
     /**
      * 请款id
      */
-    private Long paymentRequests;
+    private Long paymentRequestsId;
 
     /**
      * 费用类型
@@ -72,6 +72,6 @@ public class PaymentRequestsDetail extends BaseIdPo {
      */
     @TableLogic
     @TableField(fill = FieldFill.INSERT)
-    private Long delFlag;
+    private String delFlag;
 
 }

+ 1 - 1
jy-business/src/main/java/com/jy/business/payment/model/table/PaymentRequestsDetailTable.java

@@ -17,7 +17,7 @@ public class PaymentRequestsDetailTable extends Table<PaymentRequestsDetail> {
     /**
      * 请款id
      */
-    public QueryColumn paymentRequests = this.field(PaymentRequestsDetail::getPaymentRequests);
+    public QueryColumn paymentRequestsId = this.field(PaymentRequestsDetail::getPaymentRequestsId);
 
     /**
      * 费用类型

+ 25 - 1
jy-business/src/main/java/com/jy/business/payment/service/impl/PaymentRequestsServiceImpl.java

@@ -2,11 +2,17 @@ package com.jy.business.payment.service.impl;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jy.business.payment.dao.PaymentRequestsDao;
+import com.jy.business.payment.dao.PaymentRequestsDetailDao;
 import com.jy.business.payment.model.dto.PaymentRequestsDto;
 import com.jy.business.payment.model.dto.PaymentRequestsSelectDto;
+import com.jy.business.payment.model.entity.PaymentRequestsDetail;
 import com.jy.business.payment.model.vo.PaymentRequestsVo;
 import com.jy.business.payment.service.PaymentRequestsService;
+import com.jy.framework.model.constants.FlowConstant;
+import com.jy.framework.satoken.LoginContext;
 import com.jy.framework.utils.AssertUtil;
+import com.warm.flow.core.dto.FlowParams;
+import com.warm.flow.core.service.InsService;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -18,15 +24,21 @@ import java.util.List;
  * 请款 服务实现类
  * </p>
  *
- * @author 
+ * @author
  * @since 2024-10-18
  */
 @Service
 public class PaymentRequestsServiceImpl implements PaymentRequestsService {
 
     @Resource
+    private InsService insService;
+
+    @Resource
     private PaymentRequestsDao paymentRequestsDao;
 
+    @Resource
+    private PaymentRequestsDetailDao paymentRequestsDetailDao;
+
     @Override
     public Page<PaymentRequestsVo> getPage(PaymentRequestsSelectDto dto) {
         return paymentRequestsDao.getPage(dto);
@@ -39,11 +51,23 @@ public class PaymentRequestsServiceImpl implements PaymentRequestsService {
         return vo;
     }
 
+    @Transactional(rollbackFor = Exception.class)
     @Override
     public void add(PaymentRequestsDto dto) {
         paymentRequestsDao.save(dto);
+
+        List<PaymentRequestsDetail> paymentRequestsDetailList = dto.getPaymentRequestsDetailList();
+        paymentRequestsDetailList.forEach(item -> item.setPaymentRequestsId(dto.getId()));
+        paymentRequestsDetailDao.saveBatch(paymentRequestsDetailList);
+
+        // 启动流程
+        FlowParams flowParams = FlowParams.build()
+                .flowCode(FlowConstant.PAYMENT_REQUESTS)
+                .setHandler(LoginContext.getUserId().toString());
+        insService.start(dto.getId().toString(), flowParams);
     }
 
+    @Transactional(rollbackFor = Exception.class)
     @Override
     public void edit(PaymentRequestsDto dto) {
         paymentRequestsDao.updateById(dto);

+ 0 - 10
jy-flow/pom.xml

@@ -22,16 +22,6 @@
             <groupId>com.jy</groupId>
             <artifactId>jy-system</artifactId>
         </dependency>
-        <dependency>
-            <groupId>io.github.minliuhua</groupId>
-            <artifactId>warm-flow-mybatis-plus-sb-starter</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>com.baomidou</groupId>
-                    <artifactId>mybatis-plus</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
     </dependencies>
 
 </project>

+ 11 - 0
jy-framework/pom.xml

@@ -97,6 +97,17 @@
             <artifactId>hutool-http</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>io.github.minliuhua</groupId>
+            <artifactId>warm-flow-mybatis-plus-sb-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.baomidou</groupId>
+                    <artifactId>mybatis-plus</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 10 - 0
jy-framework/src/main/java/com/jy/framework/exception/UnifiedExceptionHandler.java

@@ -9,6 +9,7 @@ import cn.hutool.http.HttpStatus;
 import com.jy.framework.model.base.R;
 import com.jy.framework.model.event.ExceptionEvent;
 import com.jy.framework.utils.ServletUtil;
+import com.warm.flow.core.exception.FlowException;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
@@ -175,4 +176,13 @@ public class UnifiedExceptionHandler {
         };
     }
 
+    /**
+     * 角色权限异常
+     */
+    @ExceptionHandler(FlowException.class)
+    public R handleFlowException(FlowException e) {
+        log.error("流程节点异常:{}", e.getMessage(), e);
+        return R.fail("流程节点异常:{}", e.getMessage());
+    }
+
 }

+ 7 - 0
jy-framework/src/main/java/com/jy/framework/model/constants/FlowConstant.java

@@ -0,0 +1,7 @@
+package com.jy.framework.model.constants;
+
+public interface FlowConstant {
+
+    String PAYMENT_REQUESTS = "PAYMENT_REQUESTS";
+
+}

+ 1 - 0
jy-system/src/main/java/com/jy/system/dao/SysMenuDao.java

@@ -155,6 +155,7 @@ public class SysMenuDao extends BaseDao<SysMenuMapper, SysMenu> {
                         sm.isKeepAlive,
                         sm.isLink,
                         sm.isFull,
+                        sm.sort,
                         sm.perms
                 )
                 .from(sm)

+ 5 - 0
jy-ui/src/api/flow/definition.ts

@@ -70,3 +70,8 @@ export function unActiveApi(id: string): Promise<StrAnyObj> {
 export function copyDefApi(id: string): Promise<StrAnyObj> {
   return request.get(`/flow/definition/copyDef/${id}`)
 }
+
+// 查看流程图
+export function getChartApi(id: string): Promise<string> {
+  return request.get(`/flow/definition/flowChart/${id}`)
+}

+ 5 - 0
jy-ui/src/api/flow/execute.ts

@@ -5,3 +5,8 @@ import { PageType, StrAnyObj } from '@/typings'
 export function getToDoPageApi(params: StrAnyObj): Promise<PageType<StrAnyObj>> {
   return request.get('/flow/execute/todoPage', params)
 }
+
+// 查询已办任务历史记录
+export function getDoneListApi(id: string): Promise<PageType<StrAnyObj>> {
+  return request.get(`/flow/execute/doneList/${id}`)
+}

+ 9 - 3
jy-ui/src/components/ADialog/index.vue

@@ -50,10 +50,16 @@ function submit() {
     @closed="closed"
   >
     <slot />
-    <template #footer v-if="footer">
+
+    <template #footer>
       <div class="dialog-footer">
-        <el-button @click="cancel">取 消</el-button>
-        <el-button type="primary" @click="submit">确 定</el-button>
+        <div v-if="!footer">
+          <slot name="footer" />
+        </div>
+        <div v-else>
+          <el-button @click="cancel">取 消</el-button>
+          <el-button type="primary" @click="submit">确 定</el-button>
+        </div>
       </div>
     </template>
   </el-dialog>

+ 3 - 3
jy-ui/src/components/AForm/ADatePicker.vue

@@ -83,7 +83,7 @@ const computedModelValue = computed({
 <template>
   <el-date-picker
     v-model="computedModelValue"
-    :placeholder="placeholder"
+    :placeholder="disabled ? '' : placeholder"
     :clearable="clearable"
     :disabled="disabled"
     :readonly="readonly"
@@ -95,8 +95,8 @@ const computedModelValue = computed({
     :default-time="defaultTime"
     :disabled-date="disabledDate"
     :shortcuts="shortcuts"
-    :start-placeholder="startPlaceholder"
-    :end-placeholder="endPlaceholder"
+    :start-placeholder="disabled ? '' : startPlaceholder"
+    :end-placeholder="disabled ? '' : endPlaceholder"
     :range-separator="rangeSeparator"
     style="width: 100%"
     @change="(value: any) => change?.(value)"

+ 1 - 1
jy-ui/src/components/AForm/AInput.vue

@@ -48,7 +48,7 @@ const computedModelValue = computed({
 <template>
   <el-input
     v-model="computedModelValue"
-    :placeholder="placeholder"
+    :placeholder="disabled ? '' : placeholder"
     :clearable="clearable"
     :disabled="disabled"
     :readonly="readonly"

+ 1 - 1
jy-ui/src/components/AForm/AInputNumber.vue

@@ -38,7 +38,7 @@ const computedModelValue = computed({
 <template>
   <el-input-number
     v-model="computedModelValue"
-    :placeholder="placeholder"
+    :placeholder="disabled ? '' : placeholder"
     :disabled="disabled"
     :readonly="readonly"
     :style="style"

+ 1 - 1
jy-ui/src/components/AForm/ASelect.vue

@@ -85,7 +85,7 @@ function clearOption() {
 <template>
   <el-select
     v-model="computedModelValue"
-    :placeholder="placeholder"
+    :placeholder="disabled ? '' : placeholder"
     :clearable="clearable"
     :disabled="disabled"
     :style="style"

+ 1 - 1
jy-ui/src/components/FlieUpload/index.vue

@@ -102,7 +102,7 @@ const onExceed = () => {
     :limit="limit"
     :style="{ width }"
   >
-    <el-button type="primary">上传文件</el-button>
+    <el-button type="primary" :disabled="disabled">上传文件</el-button>
     <template v-if="tip" #tip>
       <div class="el-upload__tip">
         {{ tip }}

+ 36 - 11
jy-ui/src/views/business/payment/requests/managePanel.vue → jy-ui/src/views/business/payment/requests/flowDetail.vue

@@ -1,16 +1,36 @@
 <script setup lang="ts">
+import FileUpload from '@/components/FlieUpload/index.vue'
+import DeptTreeSelect from '@/views/components/DeptTreeSelect/index.vue'
 import AForm from '@/components/AForm/index.vue'
-import { FormConfigType } from '@/components/AForm/type'
-import { ColumnConfigType } from '@/components/ATable/type'
 import { StrAnyObj } from '@/typings'
+import { FormConfigType } from '@/components/AForm/type'
 import { getPageApi as getCorporationPageApi } from '@/api/business/corporation/corporation'
-import DeptTreeSelect from '@/views/components/DeptTreeSelect/index.vue'
 import { getPageApi as getCapitalAccountPageApi } from '@/api/business/capital/account'
+import { ColumnConfigType } from '@/components/ATable/type'
+import { getDetailApi } from '@/api/business/payment/requests'
 
-const formRef = ref<InstanceType<typeof AForm>>()
+const props = withDefaults(
+  defineProps<{
+    businessId: string
+    disabled?: boolean
+  }>(),
+  {
+    disabled: true
+  }
+)
 
-const formData = ref<StrAnyObj>({ paymentRequestsDetailList: [{}] })
+watch(
+  props.businessId,
+  () => {
+    nextTick(() => deptIdRef.value?.load())
+    getDetailApi({ id: props.businessId }).then((resp: StrAnyObj) => {
+      formData.value = resp
+    })
+  },
+  { immediate: true }
+)
 
+const formData = ref<StrAnyObj>({ paymentRequestsDetailList: [{}] })
 const deptIdRef = ref<InstanceType<typeof DeptTreeSelect>>()
 
 const formConfig: FormConfigType[] = [
@@ -28,7 +48,7 @@ const formConfig: FormConfigType[] = [
   },
   {
     type: 'slot',
-    prop: 'dept',
+    prop: 'deptId',
     label: '归属部门',
     rule: [{ required: true, message: '部门id不能为空', trigger: 'blur' }]
   },
@@ -174,11 +194,13 @@ function updateAmount() {
 </script>
 
 <template>
-  <a-form ref="formRef" v-model="formData" :config="formConfig" :span="12">
-    <template #dept>
-      <dept-tree-select ref="deptIdRef" v-model="formData.deptId" />
+  <a-form ref="formRef" v-model="formData" :config="formConfig" :span="12" :disabled="disabled">
+    <template #deptId>
+      <dept-tree-select ref="deptIdRef" v-model="formData.deptId" :disabled="disabled" />
+    </template>
+    <template #atts>
+      <file-upload v-model="formData.atts" :disabled="disabled" />
     </template>
-    <template #atts> </template>
     <template #detailTable>
       <a-table
         :data="formData.paymentRequestsDetailList"
@@ -193,7 +215,7 @@ function updateAmount() {
           <a-input
             v-model="scope.row.remark"
             type="textarea"
-            rows="2"
+            :rows="2"
             @change="updateDetailRemark"
           />
         </template>
@@ -207,6 +229,7 @@ function updateAmount() {
         </template>
       </a-table>
       <el-button
+        v-if="!disabled"
         style="width: 100%; margin-bottom: 20px"
         type="primary"
         @click="addPaymentRequestsDetailList"
@@ -216,3 +239,5 @@ function updateAmount() {
     </template>
   </a-form>
 </template>
+
+<style scoped lang="scss"></style>

+ 18 - 2
jy-ui/src/views/business/payment/requests/index.vue

@@ -231,7 +231,7 @@ const formConfig: FormConfigType[] = [
   },
   {
     type: 'slot',
-    prop: 'dept',
+    prop: 'deptId',
     label: '归属部门',
     rule: [{ required: true, message: '部门id不能为空', trigger: 'blur' }]
   },
@@ -374,6 +374,22 @@ function tableSelectionChange(item: StrAnyObjArr) {
 
 function formSubmit() {
   formRef.value?.validate(() => {
+    if (formData.value.paymentRequestsDetailList.length === 0) {
+      ElMessage.error('请款明细为空,无法提交')
+      return
+    }
+
+    for (const item of formData.value.paymentRequestsDetailList) {
+      if (!item.expenseType) {
+        ElMessage.error('请款明细存在费用类型为空,无法提交')
+        return
+      }
+      if (!item.amount) {
+        ElMessage.error('请款明细存在请款金额为空,无法提交')
+        return
+      }
+    }
+
     if (formData.value.id) {
       editApi(formData.value).then(() => {
         dialogVisible.value = false
@@ -452,7 +468,7 @@ function updateAmount() {
       height="200px"
     >
       <a-form ref="formRef" v-model="formData" :config="formConfig" :span="12">
-        <template #dept>
+        <template #deptId>
           <dept-tree-select ref="deptIdRef" v-model="formData.deptId" />
         </template>
         <template #atts>

+ 4 - 1
jy-ui/src/views/components/DeptTreeSelect/index.vue

@@ -7,10 +7,12 @@ const props = withDefaults(
     modelValue?: string | Array<string>
     showTop?: boolean
     multiple?: boolean
+    disabled?: boolean
   }>(),
   {
     showTop: false,
-    multiple: false
+    multiple: false,
+    disabled: false
   }
 )
 
@@ -51,6 +53,7 @@ defineExpose({ load })
     :data="treeData"
     :props="{ label: 'name' }"
     :multiple="multiple"
+    :disabled="disabled"
     node-key="id"
     placeholder="请选择部门"
     filterable

+ 1 - 1
jy-ui/src/views/flow/definition/index.vue

@@ -358,7 +358,7 @@ function handleRemove(id: string) {
 
     <design ref="designVisibleRef" v-model="designVisible" />
 
-    <a-dialog title="流程图" v-model="chartVisible" width="80%" :footer="false" append-to-body>
+    <a-dialog title="流程图" v-model="chartVisible" width="1200px" :footer="false" append-to-body>
       <img :src="imgUrl" width="100%" style="margin: 0 auto" alt="" />
     </a-dialog>
   </div>

+ 89 - 19
jy-ui/src/views/flow/taskTodo/index.vue

@@ -6,7 +6,12 @@ import { ColumnConfigType } from '@/components/ATable/type'
 import { StrAnyObj, StrAnyObjArr } from '@/typings'
 import { useHandleData } from '@/utils/useHandleData'
 import { getPageApi, getDetailApi, addApi, editApi, deleteApi } from '@/api/system/config'
-import { getToDoPageApi } from '@/api/flow/execute'
+import { getDoneListApi, getToDoPageApi } from '@/api/flow/execute'
+import { getChartApi } from '@/api/flow/definition'
+import { Comment } from '@element-plus/icons-vue'
+import { nextTick } from 'vue'
+
+const modules = import.meta.glob('@/views/**/*.vue')
 
 const queryRef = ref<InstanceType<typeof AForm>>()
 const formRef = ref<InstanceType<typeof AForm>>()
@@ -21,6 +26,17 @@ const formData = ref<StrAnyObj>({})
 const dialogTitle = ref<string>('')
 const dialogVisible = ref<boolean>(false)
 
+const chartVisible = ref(false)
+const imgUrl = ref('')
+
+const doneListVisible = ref(false)
+const doneList = ref([])
+
+const handleVisible = ref(false)
+const handleComponent = ref(null)
+const businessId = ref(null)
+const handleDisabled = ref(true)
+
 const queryConfig: FormConfigType[] = [
   {
     type: 'input',
@@ -106,24 +122,38 @@ const columnConfig: ColumnConfigType[] = [
   {
     width: 200,
     handleConfig: [
-      // {
-      //   common: 'update',
-      //   permissions: 'sysConfig:edit',
-      //   click(row) {
-      //     dialogVisible.value = true
-      //     dialogTitle.value = '编辑'
-      //     getDetailApi({ id: row.id }).then((resp: StrAnyObj) => {
-      //       formData.value = resp
-      //     })
-      //   }
-      // },
-      // {
-      //   common: 'delete',
-      //   permissions: 'sysConfig:delete',
-      //   click(row) {
-      //     handleRemove([row.id])
-      //   }
-      // }
+      {
+        text: '办理',
+        click(row) {
+          const path = `/src/views/${row.formPath}`
+          const module = modules[path]
+          if (!module) {
+            ElMessage.error(`未知流程表单路径:${path}`)
+            return
+          }
+          businessId.value = row.businessId
+          handleComponent.value = markRaw(defineAsyncComponent(module))
+          handleVisible.value = true
+        }
+      },
+      {
+        text: '流程图',
+        click(row) {
+          getChartApi(row.instanceId).then((resp) => {
+            chartVisible.value = true
+            imgUrl.value = 'data:image/gif;base64,' + resp
+          })
+        }
+      },
+      {
+        text: '审批记录',
+        click(row) {
+          getDoneListApi(row.instanceId).then((resp) => {
+            doneListVisible.value = true
+            doneList.value = resp
+          })
+        }
+      }
     ]
   }
 ]
@@ -163,6 +193,30 @@ const formConfig: FormConfigType[] = [
   }
 ]
 
+const doneListColumnConfig: ColumnConfigType[] = [
+  {
+    prop: 'nodeName',
+    label: '审批节点'
+  },
+  {
+    prop: 'targetNodeName',
+    label: '跳转节点'
+  },
+  {
+    prop: 'approver',
+    label: '审批人'
+  },
+  {
+    prop: 'createTime',
+    label: '审批时间'
+  },
+  {
+    prop: 'message',
+    label: '审批意见',
+    showOverflowTooltip: true
+  }
+]
+
 onMounted(() => {
   getPage()
 })
@@ -224,5 +278,21 @@ function formClosed() {
     >
       <a-form ref="formRef" v-model="formData" :config="formConfig" :span="24"> </a-form>
     </a-dialog>
+
+    <a-dialog title="办理" v-model="handleVisible" width="1200px" :footer="false">
+      <component :is="handleComponent" :businessId="businessId" />
+      <template #footer>
+        <el-button @click="handleVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submit">通 过</el-button>
+      </template>
+    </a-dialog>
+
+    <a-dialog title="流程图" v-model="chartVisible" width="1200px" :footer="false">
+      <img :src="imgUrl" width="100%" style="margin: 0 auto" alt="" />
+    </a-dialog>
+
+    <a-dialog title="审批记录" v-model="doneListVisible" width="1000px" :footer="false">
+      <a-table :data="doneList" :column-config="doneListColumnConfig" :card="false"> </a-table>
+    </a-dialog>
   </div>
 </template>