24282 11 meses atrás
pai
commit
95022fd06b

+ 24 - 0
jy-framework/src/main/java/com/jy/framework/config/ObsConfig.java

@@ -0,0 +1,24 @@
+package com.jy.framework.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Setter
+@Getter
+@Component
+@ConfigurationProperties(prefix = "jy.obs")
+public class ObsConfig {
+
+    private String ak;
+
+    private String sk;
+
+    private String endPoint;
+
+    private String url;
+
+    private String bucketName;
+
+}

+ 6 - 0
jy-starter/src/main/resources/application.yml

@@ -16,6 +16,12 @@ jy:
     - /logout           # 登出
     - /open/**          # 开放接口
     - /test/**          # 测试接口
+  obs:
+    ak: 9HNBVBHO7F3GLUCGTK5C
+    sk: ZowLEoMJrICA9tOyln0yWVm0xGSiupe0gnbsZimk
+    endPoint: obs.cn-south-1.myhuaweicloud.com
+    url: https://os.winfaster.cn/
+    bucketName: winfaster
 
 spring:
 

+ 5 - 0
jy-system/pom.xml

@@ -24,6 +24,11 @@
             <artifactId>easy-captcha</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.huaweicloud</groupId>
+            <artifactId>esdk-obs-java</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 66 - 0
jy-system/src/main/java/com/jy/system/controller/FileConfig.java

@@ -0,0 +1,66 @@
+package com.jy.system.controller;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.jy.framework.config.JyConfig;
+import com.jy.framework.config.ObsConfig;
+import com.jy.framework.exception.ServiceException;
+import com.jy.framework.utils.AssertUtil;
+import com.obs.services.ObsClient;
+import jakarta.annotation.Resource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.StringJoiner;
+
+@RestController
+@RequestMapping("/file")
+public class FileConfig {
+
+    @Resource
+    private ObsConfig obsConfig;
+
+    @Resource
+    private JyConfig jsonConfig;
+
+    @Value("${spring.profiles.active}")
+    private String active;
+
+    @PostMapping("/upload")
+    public Map<String, String> uploadFile(@RequestParam("file") MultipartFile file) {
+        AssertUtil.notEmpty(file, "文件不存在");
+
+        String oriName = file.getOriginalFilename();
+        AssertUtil.notBlank(oriName, "文件名为空");
+
+        // 文件后缀名
+        String suffix = FileUtil.getSuffix(oriName);
+
+        // 文件路径
+        String objectKey = new StringJoiner("/")
+                .add(jsonConfig.getProjectName())
+                .add(active)
+                .add(DateUtil.format(new Date(), "yyyy/MM/dd"))
+                .add(IdUtil.fastSimpleUUID() + (ObjectUtil.isEmpty(suffix) ? "" : "." + suffix))
+                .toString();
+
+        // 上传文件
+        try (ObsClient obsClient = new ObsClient(obsConfig.getAk(), obsConfig.getSk(), obsConfig.getEndPoint())) {
+            obsClient.putObject(obsConfig.getBucketName(), objectKey, file.getInputStream());
+        } catch (Exception e) {
+            throw new ServiceException("上传失败");
+        }
+
+        // 返回文件路径
+        return Map.of("name", oriName, "url", obsConfig.getUrl() + objectKey);
+    }
+
+}

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

@@ -13,7 +13,7 @@ const props = withDefaults(
       unknown
     >
     style?: StrAnyObj
-    placeholder: string
+    placeholder?: string
     clearable?: boolean
     disabled?: boolean
     readonly?: boolean

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

@@ -12,7 +12,7 @@ const props = withDefaults(
       unknown
     >
     style?: StrAnyObj
-    placeholder: string
+    placeholder?: string
     clearable?: boolean
     disabled?: boolean
     readonly?: boolean

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

@@ -5,7 +5,7 @@ const props = withDefaults(
   defineProps<{
     modelValue: number | undefined
     style?: StrAnyObj
-    placeholder: string
+    placeholder?: string
     disabled?: boolean
     readonly?: boolean
     min?: number

+ 72 - 0
jy-ui/src/components/FlieUpload/index.vue

@@ -0,0 +1,72 @@
+<script setup lang="ts">
+import { StrAnyObj } from '@/typings'
+import { getToken, removeToken } from '@/utils'
+import { ElMessage } from 'element-plus'
+import router from '@/router'
+
+const props = withDefaults(
+  defineProps<{
+    modelValue?: string
+    tip?: string
+    limit?: number
+  }>(),
+  {}
+)
+
+const emits = defineEmits(['update:modelValue'])
+
+const fileList = computed({
+  get() {
+    return props.modelValue ? JSON.parse(props.modelValue) : []
+  },
+  set(newValue) {
+    emits('update:modelValue', JSON.stringify(newValue))
+  }
+})
+const headers = ref({})
+const action = import.meta.env.VITE_APP_BASE_API + '/file/upload'
+
+const beforeUpload = () => {
+  headers.value = { Authorization: getToken() }
+}
+
+const onSuccess = (resp: StrAnyObj) => {
+  console.log(resp)
+  // 登录失效
+  if (resp.code == 401) {
+    removeToken()
+    ElMessage.error(resp.message)
+    router.replace(LOGIN_URL).then()
+    return Promise.reject(resp)
+  }
+  else if (resp.code != 200) {
+    ElMessage.error(resp.message)
+    return Promise.reject(resp)
+  }
+  fileList.value.push(resp.data)
+  ElMessage.success('上传成功')
+}
+</script>
+
+<template>
+  <el-upload
+    v-model:file-list="fileList"
+    class="upload-demo"
+    :headers="headers"
+    :action="action"
+    :multiple="limit > 1"
+    :before-upload="beforeUpload"
+    :on-success="onSuccess"
+    :limit="limit"
+    style="{width: 100%}"
+  >
+    <el-button type="primary">上传文件</el-button>
+    <template v-if="tip" #tip>
+      <div class="el-upload__tip">
+        {{ tip }}
+      </div>
+    </template>
+  </el-upload>
+</template>
+
+<style scoped lang="scss"></style>

+ 3 - 5
jy-ui/src/utils/request.ts

@@ -63,11 +63,9 @@ class RequestHttp {
 
         // 登录失效
         if (data.code == 401) {
-          if (getToken()) {
-            removeToken()
-            ElMessage.error(data.message)
-            router.replace(LOGIN_URL).then()
-          }
+          removeToken()
+          ElMessage.error(data.message)
+          router.replace(LOGIN_URL).then()
           return Promise.reject(data)
         }
 

+ 40 - 20
jy-ui/src/views/business/payment/requests/index.vue

@@ -12,9 +12,10 @@ import {
   editApi,
   deleteApi
 } from '@/api/business/payment/requests'
-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 { 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 FileUpload from '@/components/FlieUpload/index.vue'
 
 const queryRef = ref<InstanceType<typeof AForm>>()
 const formRef = ref<InstanceType<typeof AForm>>()
@@ -222,7 +223,7 @@ const formConfig: FormConfigType[] = [
     label: '归属公司',
     keyName: 'id',
     labelName: 'name',
-    async option ()  {
+    async option() {
       const data = await getCorporationPageApi({ searchAll: true })
       return data.records
     },
@@ -307,22 +308,22 @@ const formConfig: FormConfigType[] = [
   {
     type: 'input',
     prop: 'accountName',
-    label: '户名',
+    label: '户名'
   },
   {
     type: 'input',
     prop: 'account',
-    label: '银行账号',
+    label: '银行账号'
   },
   {
     type: 'input',
     prop: 'depositBank',
-    label: '开户银行',
+    label: '开户银行'
   },
   {
     type: 'input',
     prop: 'correspondentNumber',
-    label: '联行号/SWIFT Code',
+    label: '联行号/SWIFT Code'
   }
 ]
 
@@ -334,7 +335,7 @@ const detailTableColumnConfig: ColumnConfigType[] = [
   },
   {
     slot: 'remark',
-    label: '款项说明',
+    label: '款项说明'
   },
   {
     slot: 'amount',
@@ -409,17 +410,16 @@ function addPaymentRequestsDetailList() {
 function updateDetailRemark() {
   formData.value.useRemark = formData.value.paymentRequestsDetailList
     .map((item) => item.remark)
-      .filter((item) => item !== '' && item !== null && item !== undefined)
-      .join(' - \n')
+    .filter((item) => item !== '' && item !== null && item !== undefined)
+    .join(' - \n')
 }
 
 function updateAmount() {
   formData.value.totalAmount = formData.value.paymentRequestsDetailList
-      .map((item) => item.amount)
-      .filter((item) => item !== '' && item !== null && item !== undefined)
-      .reduce((pre, next) => pre + next, 0)
+    .map((item) => item.amount)
+    .filter((item) => item !== '' && item !== null && item !== undefined)
+    .reduce((pre, next) => pre + next, 0)
 }
-
 </script>
 
 <template>
@@ -456,20 +456,40 @@ function updateAmount() {
           <dept-tree-select ref="deptIdRef" v-model="formData.deptId" />
         </template>
         <template #atts>
+          <file-upload v-model="formData.atts" style="width: 50%" />
         </template>
         <template #detailTable>
-          <a-table :data="formData.paymentRequestsDetailList" :columnConfig="detailTableColumnConfig" style="width: 100%" :card="false">
+          <a-table
+            :data="formData.paymentRequestsDetailList"
+            :columnConfig="detailTableColumnConfig"
+            style="width: 100%"
+            :card="false"
+          >
             <template #expenseType="scope">
-              <a-select v-model="scope.row.expenseType" dict="expense_type"/>
+              <a-select v-model="scope.row.expenseType" dict="expense_type" />
             </template>
             <template #remark="scope">
-              <a-input v-model="scope.row.remark" type="textarea" rows="2" @change="updateDetailRemark"/>
+              <a-input
+                v-model="scope.row.remark"
+                type="textarea"
+                :rows="2"
+                @change="updateDetailRemark"
+              />
             </template>
             <template #amount="scope">
-              <a-inputNumber v-model="scope.row.amount" :min="0.01" :precision="2" @change="updateAmount" />
+              <a-inputNumber
+                v-model="scope.row.amount"
+                :min="0.01"
+                :precision="2"
+                @change="updateAmount"
+              />
             </template>
           </a-table>
-          <el-button style="width: 100%; margin-bottom: 20px" type="primary" @click="addPaymentRequestsDetailList">
+          <el-button
+            style="width: 100%; margin-bottom: 20px"
+            type="primary"
+            @click="addPaymentRequestsDetailList"
+          >
             添加行
           </el-button>
         </template>

+ 40 - 23
jy-ui/src/views/business/payment/requests/managePanel.vue

@@ -3,9 +3,9 @@ import AForm from '@/components/AForm/index.vue'
 import { FormConfigType } from '@/components/AForm/type'
 import { ColumnConfigType } from '@/components/ATable/type'
 import { StrAnyObj } from '@/typings'
-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 { 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'
 
 const formRef = ref<InstanceType<typeof AForm>>()
 
@@ -20,7 +20,7 @@ const formConfig: FormConfigType[] = [
     label: '归属公司',
     keyName: 'id',
     labelName: 'name',
-    async option ()  {
+    async option() {
       const data = await getCorporationPageApi({ searchAll: true })
       return data.records
     },
@@ -105,22 +105,22 @@ const formConfig: FormConfigType[] = [
   {
     type: 'input',
     prop: 'accountName',
-    label: '户名',
+    label: '户名'
   },
   {
     type: 'input',
     prop: 'account',
-    label: '银行账号',
+    label: '银行账号'
   },
   {
     type: 'input',
     prop: 'depositBank',
-    label: '开户银行',
+    label: '开户银行'
   },
   {
     type: 'input',
     prop: 'correspondentNumber',
-    label: '联行号/SWIFT Code',
+    label: '联行号/SWIFT Code'
   }
 ]
 
@@ -132,7 +132,7 @@ const detailTableColumnConfig: ColumnConfigType[] = [
   },
   {
     slot: 'remark',
-    label: '款项说明',
+    label: '款项说明'
   },
   {
     slot: 'amount',
@@ -160,18 +160,17 @@ function addPaymentRequestsDetailList() {
 
 function updateDetailRemark() {
   formData.value.useRemark = formData.value.paymentRequestsDetailList
-      .map((item) => item.remark)
-      .filter((item) => item !== '' && item !== null && item !== undefined)
-      .join(' - \n')
+    .map((item) => item.remark)
+    .filter((item) => item !== '' && item !== null && item !== undefined)
+    .join(' - \n')
 }
 
 function updateAmount() {
   formData.value.totalAmount = formData.value.paymentRequestsDetailList
-      .map((item) => item.amount)
-      .filter((item) => item !== '' && item !== null && item !== undefined)
-      .reduce((pre, next) => pre + next, 0)
+    .map((item) => item.amount)
+    .filter((item) => item !== '' && item !== null && item !== undefined)
+    .reduce((pre, next) => pre + next, 0)
 }
-
 </script>
 
 <template>
@@ -179,21 +178,39 @@ function updateAmount() {
     <template #dept>
       <dept-tree-select ref="deptIdRef" v-model="formData.deptId" />
     </template>
-    <template #atts>
-    </template>
+    <template #atts> </template>
     <template #detailTable>
-      <a-table :data="formData.paymentRequestsDetailList" :columnConfig="detailTableColumnConfig" style="width: 100%" :card="false">
+      <a-table
+        :data="formData.paymentRequestsDetailList"
+        :columnConfig="detailTableColumnConfig"
+        style="width: 100%"
+        :card="false"
+      >
         <template #expenseType="scope">
-          <a-select v-model="scope.row.expenseType" dict="expense_type"/>
+          <a-select v-model="scope.row.expenseType" dict="expense_type" />
         </template>
         <template #remark="scope">
-          <a-input v-model="scope.row.remark" type="textarea" rows="2" @change="updateDetailRemark"/>
+          <a-input
+            v-model="scope.row.remark"
+            type="textarea"
+            rows="2"
+            @change="updateDetailRemark"
+          />
         </template>
         <template #amount="scope">
-          <a-inputNumber v-model="scope.row.amount" :min="0.01" :precision="2" @change="updateAmount" />
+          <a-inputNumber
+            v-model="scope.row.amount"
+            :min="0.01"
+            :precision="2"
+            @change="updateAmount"
+          />
         </template>
       </a-table>
-      <el-button style="width: 100%; margin-bottom: 20px" type="primary" @click="addPaymentRequestsDetailList">
+      <el-button
+        style="width: 100%; margin-bottom: 20px"
+        type="primary"
+        @click="addPaymentRequestsDetailList"
+      >
         添加行
       </el-button>
     </template>

+ 7 - 0
pom.xml

@@ -28,6 +28,7 @@
         <nashorn-core.version>15.4</nashorn-core.version>
         <beetl.version>3.17.0.RELEASE</beetl.version>
         <warm-flow.version>1.2.10</warm-flow.version>
+        <esdk-obs-java.version>3.19.7</esdk-obs-java.version>
     </properties>
 
     <modules>
@@ -125,6 +126,12 @@
             </dependency>
 
             <dependency>
+                <groupId>com.huaweicloud</groupId>
+                <artifactId>esdk-obs-java</artifactId>
+                <version>${esdk-obs-java.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>com.jy</groupId>
                 <artifactId>jy-starter</artifactId>
                 <version>${jy.version}</version>