张庆宇 vor 3 Wochen
Commit
caedb3ff3b

+ 42
- 0
.gitignore Datei anzeigen

@@ -0,0 +1,42 @@
1
+HELP.md
2
+
3
+!.mvn/wrapper/maven-wrapper.jar
4
+!**/src/main/**
5
+!**/src/test/**
6
+
7
+### STS ###
8
+.apt_generated
9
+.classpath
10
+.factorypath
11
+.project
12
+.settings
13
+.springBeans
14
+.sts4-cache
15
+
16
+### IntelliJ IDEA ###
17
+.idea
18
+/.idea/
19
+*.iws
20
+*.iml
21
+*.ipr
22
+/.idea
23
+logs/
24
+target/
25
+### Hbuilder Vue ### 
26
+node_modules/
27
+/dist/
28
+### Hbuilder uniapp ###
29
+/unpackage/release
30
+### NetBeans ###
31
+/nbproject/private/
32
+/nbbuild/
33
+/dist/
34
+/nbdist/
35
+/.nb-gradle/
36
+build/
37
+
38
+### VS Code ###
39
+.vscode/
40
+
41
+
42
+

+ 13
- 0
index.html Datei anzeigen

@@ -0,0 +1,13 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="UTF-8" />
5
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+    <title>Vue3 项目</title>
8
+  </head>
9
+  <body>
10
+    <div id="app"></div>
11
+    <script type="module" src="/src/main.js"></script>
12
+  </body>
13
+</html>

+ 1675
- 0
package-lock.json
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 23
- 0
package.json Datei anzeigen

@@ -0,0 +1,23 @@
1
+{
2
+  "name": "frontend",
3
+  "private": true,
4
+  "version": "0.0.0",
5
+  "type": "module",
6
+  "scripts": {
7
+    "dev": "vite",
8
+    "build": "vite build",
9
+    "preview": "vite preview"
10
+  },
11
+  "dependencies": {
12
+    "@element-plus/icons-vue": "^2.3.2",
13
+    "axios": "^1.6.7",
14
+    "element-plus": "^2.11.7",
15
+    "vue": "^3.4.21",
16
+    "vue-router": "^4.3.0",
17
+    "xlsx": "^0.18.5"
18
+  },
19
+  "devDependencies": {
20
+    "@vitejs/plugin-vue": "^5.0.4",
21
+    "vite": "^5.1.6"
22
+  }
23
+}

+ 31
- 0
src/App.vue Datei anzeigen

@@ -0,0 +1,31 @@
1
+<template>
2
+  <div id="app">
3
+    <router-view/>
4
+  </div>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+  name: 'App'
10
+}
11
+</script>
12
+
13
+<style>
14
+#app {
15
+  font-family: Avenir, Helvetica, Arial, sans-serif;
16
+  -webkit-font-smoothing: antialiased;
17
+  -moz-osx-font-smoothing: grayscale;
18
+  color: #2c3e50;
19
+  padding: 0;
20
+  margin: 0;
21
+}
22
+
23
+* {
24
+  box-sizing: border-box;
25
+}
26
+
27
+body {
28
+  margin: 0;
29
+  padding: 0;
30
+}
31
+</style>

+ 25
- 0
src/api/contractApi.js Datei anzeigen

@@ -0,0 +1,25 @@
1
+import axios from 'axios'
2
+
3
+// 创建axios实例
4
+const service = axios.create({
5
+  baseURL: '/api',
6
+  timeout: 10000
7
+})
8
+
9
+// 导入销售合同Excel
10
+export const importContractExcel = (file) => {
11
+  const formData = new FormData()
12
+  formData.append('file', file)
13
+  return service({
14
+    url: '/contract/import',
15
+    method: 'post',
16
+    data: formData,
17
+    headers: {
18
+      'Content-Type': 'multipart/form-data'
19
+    }
20
+  })
21
+}
22
+
23
+export default {
24
+  importContractExcel
25
+}

+ 57
- 0
src/api/userApi.js Datei anzeigen

@@ -0,0 +1,57 @@
1
+import axios from 'axios'
2
+
3
+// 创建axios实例
4
+const service = axios.create({
5
+  baseURL: 'http://localhost:8080/api', // 后端API基础URL
6
+  timeout: 5000
7
+})
8
+
9
+// 请求拦截器
10
+service.interceptors.request.use(
11
+  config => {
12
+    // 可以在这里添加token等认证信息
13
+    return config
14
+  },
15
+  error => {
16
+    console.error('请求错误:', error)
17
+    return Promise.reject(error)
18
+  }
19
+)
20
+
21
+// 响应拦截器
22
+service.interceptors.response.use(
23
+  response => {
24
+    return response
25
+  },
26
+  error => {
27
+    console.error('响应错误:', error)
28
+    return Promise.reject(error)
29
+  }
30
+)
31
+
32
+// 获取所有用户
33
+export const getUsers = () => {
34
+  return service.get('/user')
35
+}
36
+
37
+// 根据ID获取用户
38
+export const getUserById = (id) => {
39
+  return service.get(`/user/${id}`)
40
+}
41
+
42
+// 添加用户
43
+export const addUser = (user) => {
44
+  return service.post('/user', user)
45
+}
46
+
47
+// 更新用户
48
+export const updateUser = (user) => {
49
+  return service.put('/user', user)
50
+}
51
+
52
+// 删除用户
53
+export const deleteUserById = (id) => {
54
+  return service.delete(`/user/${id}`)
55
+}
56
+
57
+export default service

+ 79
- 0
src/assets/main.css Datei anzeigen

@@ -0,0 +1,79 @@
1
+:root {
2
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+  line-height: 1.5;
4
+  font-weight: 400;
5
+
6
+  color-scheme: light dark;
7
+  color: rgba(255, 255, 255, 0.87);
8
+  background-color: #242424;
9
+
10
+  font-synthesis: none;
11
+  text-rendering: optimizeLegibility;
12
+  -webkit-font-smoothing: antialiased;
13
+  -moz-osx-font-smoothing: grayscale;
14
+}
15
+
16
+a {
17
+  font-weight: 500;
18
+  color: #646cff;
19
+  text-decoration: inherit;
20
+}
21
+a:hover {
22
+  color: #535bf2;
23
+}
24
+
25
+body {
26
+  margin: 0;
27
+  display: flex;
28
+  place-items: center;
29
+  min-width: 320px;
30
+  min-height: 100vh;
31
+}
32
+
33
+h1 {
34
+  font-size: 3.2em;
35
+  line-height: 1.1;
36
+}
37
+
38
+button {
39
+  border-radius: 8px;
40
+  border: 1px solid transparent;
41
+  padding: 0.6em 1.2em;
42
+  font-size: 1em;
43
+  font-weight: 500;
44
+  font-family: inherit;
45
+  background-color: #1a1a1a;
46
+  cursor: pointer;
47
+  transition: border-color 0.25s;
48
+}
49
+button:hover {
50
+  border-color: #646cff;
51
+}
52
+button:focus,
53
+button:focus-visible {
54
+  outline: 4px auto -webkit-focus-ring-color;
55
+}
56
+
57
+.card {
58
+  padding: 2em;
59
+}
60
+
61
+#app {
62
+  max-width: 1280px;
63
+  margin: 0 auto;
64
+  padding: 2rem;
65
+  text-align: center;
66
+}
67
+
68
+@media (prefers-color-scheme: light) {
69
+  :root {
70
+    color: #213547;
71
+    background-color: #ffffff;
72
+  }
73
+  a:hover {
74
+    color: #747bff;
75
+  }
76
+  button {
77
+    background-color: #f9f9f9;
78
+  }
79
+}

+ 13
- 0
src/main.js Datei anzeigen

@@ -0,0 +1,13 @@
1
+import { createApp } from 'vue'
2
+import App from './App.vue'
3
+import router from './router'
4
+import './assets/main.css'
5
+import ElementPlus from 'element-plus'
6
+import 'element-plus/dist/index.css'
7
+
8
+const app = createApp(App)
9
+
10
+app.use(router)
11
+app.use(ElementPlus)
12
+
13
+app.mount('#app')

+ 41
- 0
src/router/index.js Datei anzeigen

@@ -0,0 +1,41 @@
1
+import { createRouter, createWebHistory } from 'vue-router'
2
+import SalesContractView from '../views/SalesContractView.vue'
3
+import PurchaseContractView from '../views/PurchaseContractView.vue'
4
+import UserList from '../views/UserList.vue'
5
+import UserAdd from '../views/UserAdd.vue'
6
+import UserEdit from '../views/UserEdit.vue'
7
+
8
+const routes = [
9
+  {
10
+    path: '/',
11
+    name: 'contract',
12
+    component: SalesContractView
13
+  },
14
+  {
15
+    path: '/PC',
16
+    name: 'PC',
17
+    component: PurchaseContractView
18
+  },
19
+  {
20
+    path: '/users',
21
+    name: 'userList',
22
+    component: UserList
23
+  },
24
+  {
25
+    path: '/user/add',
26
+    name: 'userAdd',
27
+    component: UserAdd
28
+  },
29
+  {
30
+    path: '/user/edit/:id',
31
+    name: 'userEdit',
32
+    component: UserEdit
33
+  }
34
+]
35
+
36
+const router = createRouter({
37
+  history: createWebHistory(import.meta.env.BASE_URL),
38
+  routes
39
+})
40
+
41
+export default router

+ 38
- 0
src/views/HomeView.vue Datei anzeigen

@@ -0,0 +1,38 @@
1
+<template>
2
+  <div class="home">
3
+    <h1>欢迎使用 Vue3 + Spring Boot 项目</h1>
4
+    <p>这是一个基于 JDK8 + Vue3 的前后端分离项目模板</p>
5
+    <div class="btn-group">
6
+      <router-link to="/users">
7
+        <button>用户管理</button>
8
+      </router-link>
9
+    </div>
10
+  </div>
11
+</template>
12
+
13
+<script setup>
14
+import { ref, onMounted } from 'vue'
15
+import { useRouter } from 'vue-router'
16
+
17
+const router = useRouter()
18
+
19
+onMounted(() => {
20
+  console.log('HomeView mounted')
21
+})
22
+</script>
23
+
24
+<style scoped>
25
+.home {
26
+  display: flex;
27
+  flex-direction: column;
28
+  align-items: center;
29
+  justify-content: center;
30
+  height: 80vh;
31
+  gap: 2rem;
32
+}
33
+
34
+.btn-group {
35
+  display: flex;
36
+  gap: 1rem;
37
+}
38
+</style>

+ 943
- 0
src/views/PurchaseContractView.vue Datei anzeigen

@@ -0,0 +1,943 @@
1
+<template>
2
+  <div class="contract-container">
3
+    <h1>采购合同管理 - 期初数据批量导入</h1>
4
+    
5
+    <!-- 文件导入区域 -->
6
+    <div class="import-section">
7
+      <el-upload
8
+        class="upload-demo"
9
+        action=""
10
+        :auto-upload="false"
11
+        :on-change="handleFileChange"
12
+        :show-file-list="true"
13
+        accept=".xlsx,.xls"
14
+        :multiple="false"
15
+        drag
16
+      >
17
+        <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
18
+        <div class="el-upload__text">
19
+          拖放文件到此处,或 <em>点击上传</em>
20
+        </div>
21
+        <template #tip>
22
+          <div class="el-upload__tip">
23
+            请上传符合模板格式的Excel文件(.xlsx/.xls),支持"采购合同导入模板2.0改.xlsx"格式
24
+          </div>
25
+        </template>
26
+      </el-upload>
27
+      
28
+      <div class="import-actions">
29
+        <el-button type="primary" @click="handleImport" :disabled="!selectedFile" :loading="importLoading">
30
+          {{ importLoading ? '导入中...' : '导入文件' }}
31
+        </el-button>
32
+        <el-button @click="handleDownloadTemplate">下载模板</el-button>
33
+        <el-button type="warning" @click="handleBatchSave" :disabled="!contractsData || contractsData.main.length === 0" :loading="saveLoading">
34
+          {{ saveLoading ? '批量保存中...' : '批量保存所有合同' }}
35
+        </el-button>
36
+      </div>
37
+
38
+      <!-- 导入进度和统计信息 -->
39
+      <div v-if="importStats" class="import-stats">
40
+        <el-alert
41
+          title="导入统计"
42
+          type="info"
43
+          :closable="false"
44
+          show-icon
45
+        >
46
+          <div class="stats-content">
47
+            <span>合同总数: {{ importStats.contractCount }} 个</span>
48
+            <span>产品明细: {{ importStats.productsCount }} 条</span>
49
+            <span>付款计划: {{ importStats.paymentsCount }} 条</span>
50
+            <span>责任中心: {{ importStats.responsibilityCount }} 条</span>
51
+          </div>
52
+        </el-alert>
53
+      </div>
54
+    </div>
55
+    
56
+    <!-- 合同数据展示区域 -->
57
+    <div class="contracts-info" v-if="contractsData && contractsData.main.length > 0">
58
+      <el-tabs v-model="activeTab" type="border-card">
59
+        <!-- 合同主表信息 -->
60
+        <el-tab-pane label="合同基本信息" name="main">
61
+          <div class="table-section">
62
+            <el-table 
63
+              :data="paginatedContracts" 
64
+              style="width: 100%" 
65
+              v-loading="tableLoading"
66
+              border
67
+              height="600"
68
+              :default-sort="{ prop: 'fno', order: 'ascending' }"
69
+            >
70
+              <el-table-column prop="fno" label="采购合同号" width="180" fixed="left"></el-table-column>
71
+              <el-table-column prop="contractName" label="合同名称" width="200"></el-table-column>
72
+              <el-table-column prop="bizType" label="合同类型" width="120"></el-table-column>
73
+              <el-table-column prop="bizProductType" label="合同产品类型" width="120"></el-table-column>
74
+              <el-table-column prop="serviceType" label="业务类型" width="120"></el-table-column>
75
+              <el-table-column prop="curcy" label="币别" width="100"></el-table-column>
76
+              <el-table-column prop="supName" label="供应商名称" width="200"></el-table-column>
77
+              <el-table-column prop="supCode" label="供应商代码" width="120"></el-table-column>
78
+              <el-table-column prop="supContact" label="供应商联系人" width="120"></el-table-column>
79
+              <el-table-column prop="supBank" label="供应商开户银行" width="180"></el-table-column>
80
+              <el-table-column prop="supAcc" label="供应商银行账户" width="150"></el-table-column>
81
+              <el-table-column prop="fours" label="我方编号" width="120"></el-table-column>
82
+              <el-table-column prop="foursname" label="我方名称" width="180"></el-table-column>
83
+              <el-table-column prop="ourBankNm" label="我方开户银行" width="180"></el-table-column>
84
+              <el-table-column prop="ourBankAcct" label="我方银行账号" width="150"></el-table-column>
85
+              <el-table-column prop="createBy" label="制单人代码" width="120"></el-table-column>
86
+              <el-table-column prop="createByName" label="制单人名称" width="120"></el-table-column>
87
+              <el-table-column prop="contrExecCd" label="合同执行人代码" width="140"></el-table-column>
88
+              <el-table-column prop="contrExecNm" label="合同执行人名称" width="140"></el-table-column>
89
+              <el-table-column prop="ifLongTerm" label="是否长协" width="100">
90
+                <template #default="scope">
91
+                  <el-tag :type="scope.row.ifLongTerm === '是' ? 'success' : 'info'">
92
+                    {{ scope.row.ifLongTerm || '否' }}
93
+                  </el-tag>
94
+                </template>
95
+              </el-table-column>
96
+              <el-table-column prop="ifClearBottom" label="是否清底" width="100">
97
+                <template #default="scope">
98
+                  <el-tag :type="scope.row.ifClearBottom === '是' ? 'success' : 'info'">
99
+                    {{ scope.row.ifClearBottom || '否' }}
100
+                  </el-tag>
101
+                </template>
102
+              </el-table-column>
103
+              <el-table-column prop="fundOccupyFlg" label="是否占用资金" width="120">
104
+                <template #default="scope">
105
+                  <el-tag :type="scope.row.fundOccupyFlg === '是' ? 'success' : 'info'">
106
+                    {{ scope.row.fundOccupyFlg || '否' }}
107
+                  </el-tag>
108
+                </template>
109
+              </el-table-column>
110
+              <el-table-column prop="ifBidding" label="是否招投标" width="120">
111
+                <template #default="scope">
112
+                  {{ scope.row.ifBidding || '-' }}
113
+                </template>
114
+              </el-table-column>
115
+              <el-table-column prop="lPort" label="装运港" width="120"></el-table-column>
116
+              <el-table-column prop="lPortCtry" label="装运国" width="120"></el-table-column>
117
+              <el-table-column prop="dPort" label="目的港" width="120"></el-table-column>
118
+              <el-table-column prop="dPortCtry" label="目的国" width="120"></el-table-column>
119
+              <el-table-column prop="tranWay" label="运输方式" width="120"></el-table-column>
120
+              <el-table-column prop="delivMtd" label="交货方式" width="120"></el-table-column>
121
+              <el-table-column prop="delivLoc" label="交货地点" width="120"></el-table-column>
122
+              <el-table-column prop="estArrDt" label="最迟集港日期" width="120"></el-table-column>
123
+              <el-table-column prop="estLoadDt" label="最迟装运日期" width="120"></el-table-column>
124
+              <el-table-column prop="whetherChartering" label="是否租船" width="100">
125
+                <template #default="scope">
126
+                  <el-tag :type="scope.row.whetherChartering === '是' ? 'success' : 'info'">
127
+                    {{ scope.row.whetherChartering || '否' }}
128
+                  </el-tag>
129
+                </template>
130
+              </el-table-column>
131
+              <el-table-column prop="chnShipName" label="中文船名" width="120"></el-table-column>
132
+              <el-table-column prop="engShipName" label="英文船名" width="120"></el-table-column>
133
+              <el-table-column prop="terms" label="价格条款" width="120"></el-table-column>
134
+              <el-table-column prop="rate" label="人民币汇率" width="120">
135
+                <template #default="scope">
136
+                  {{ formatNumber(scope.row.rate) }}
137
+                </template>
138
+              </el-table-column>
139
+              <el-table-column prop="rateUs" label="美元汇率" width="120">
140
+                <template #default="scope">
141
+                  {{ formatNumber(scope.row.rateUs) }}
142
+                </template>
143
+              </el-table-column>
144
+              <el-table-column prop="qtyOver" label="数量溢装%" width="120">
145
+                <template #default="scope">
146
+                  {{ scope.row.qtyOver || '0' }}
147
+                </template>
148
+              </el-table-column>
149
+              <el-table-column prop="qtyShort" label="数量短装%" width="120">
150
+                <template #default="scope">
151
+                  {{ scope.row.qtyShort || '0' }}
152
+                </template>
153
+              </el-table-column>
154
+              <el-table-column prop="amtOver" label="金额溢装%" width="120">
155
+                <template #default="scope">
156
+                  {{ scope.row.amtOver || '0' }}
157
+                </template>
158
+              </el-table-column>
159
+              <el-table-column prop="amtShort" label="金额短装%" width="120">
160
+                <template #default="scope">
161
+                  {{ scope.row.amtShort || '0' }}
162
+                </template>
163
+              </el-table-column>
164
+              <el-table-column prop="insuranceType" label="保险种类" width="120"></el-table-column>
165
+              <el-table-column prop="insuranceAmt" label="保险费率%" width="120">
166
+                <template #default="scope">
167
+                  {{ scope.row.insuranceAmt || '0' }}
168
+                </template>
169
+              </el-table-column>
170
+              <el-table-column prop="signLoc" label="签约地点" width="120"></el-table-column>
171
+              <el-table-column prop="contrDesc" label="合同描述" width="200">
172
+                <template #default="scope">
173
+                  <el-tooltip :content="scope.row.contrDesc" placement="top" v-if="scope.row.contrDesc">
174
+                    <span class="text-ellipsis">{{ scope.row.contrDesc }}</span>
175
+                  </el-tooltip>
176
+                  <span v-else>-</span>
177
+                </template>
178
+              </el-table-column>
179
+              <el-table-column prop="remark" label="备注" width="200">
180
+                <template #default="scope">
181
+                  <el-tooltip :content="scope.row.remark" placement="top" v-if="scope.row.remark">
182
+                    <span class="text-ellipsis">{{ scope.row.remark }}</span>
183
+                  </el-tooltip>
184
+                  <span v-else>-</span>
185
+                </template>
186
+              </el-table-column>
187
+            </el-table>
188
+            <div class="pagination-section" v-if="contractsData.main.length > 0">
189
+              <el-pagination
190
+                :total="contractsData.main.length"
191
+                :page-size="pageSize"
192
+                :current-page="currentPage"
193
+                @current-change="handlePageChange"
194
+                layout="total, prev, pager, next, jumper"
195
+              />
196
+            </div>
197
+          </div>
198
+        </el-tab-pane>
199
+        
200
+        <!-- 合同产品明细 -->
201
+        <el-tab-pane label="合同产品明细" name="products">
202
+          <div class="table-section">
203
+            <el-table 
204
+              :data="paginatedProducts" 
205
+              style="width: 100%" 
206
+              height="600"
207
+              v-loading="tableLoading"
208
+              border
209
+              :default-sort="{ prop: 'fno', order: 'ascending' }"
210
+            >
211
+              <el-table-column prop="fno" label="采购合同号" width="150" fixed="left"></el-table-column>
212
+              <el-table-column prop="itemno" label="商品编号" width="120"></el-table-column>
213
+              <el-table-column prop="sdesc" label="中文品名" width="150"></el-table-column>
214
+              <el-table-column prop="edesc" label="英文品名" width="150"></el-table-column>
215
+              <el-table-column prop="brand" label="牌号" width="100">
216
+                <template #default="scope">
217
+                  {{ scope.row.brand || '-' }}
218
+                </template>
219
+              </el-table-column>
220
+              <el-table-column prop="ftype3" label="规格描述" width="120">
221
+                <template #default="scope">
222
+                  {{ scope.row.ftype3 || '-' }}
223
+                </template>
224
+              </el-table-column>
225
+              <el-table-column prop="resourceNo" label="资源号" width="120">
226
+                <template #default="scope">
227
+                  {{ scope.row.resourceNo || '-' }}
228
+                </template>
229
+              </el-table-column>
230
+              <el-table-column prop="SMRNo" label="钢厂资源号" width="120">
231
+                <template #default="scope">
232
+                  {{ scope.row.SMRNo || '-' }}
233
+                </template>
234
+              </el-table-column>
235
+              <el-table-column prop="batchNo" label="批次号" width="120">
236
+                <template #default="scope">
237
+                  {{ scope.row.batchNo || '-' }}
238
+                </template>
239
+              </el-table-column>
240
+              <el-table-column prop="curcyPo" label="采购币种" width="100"></el-table-column>
241
+              <el-table-column prop="qty" label="数量" width="100">
242
+                <template #default="scope">
243
+                  {{ formatNumber(scope.row.qty) }}
244
+                </template>
245
+              </el-table-column>
246
+              <el-table-column prop="poprice" label="采购单价" width="100">
247
+                <template #default="scope">
248
+                  {{ formatNumber(scope.row.poprice) }}
249
+                </template>
250
+              </el-table-column>
251
+              <el-table-column prop="poamt" label="金额" width="100">
252
+                <template #default="scope">
253
+                  {{ formatNumber(scope.row.poamt) }}
254
+                </template>
255
+              </el-table-column>
256
+              <el-table-column prop="inTaxRate" label="增值税率%" width="100">
257
+                <template #default="scope">
258
+                  {{ scope.row.inTaxRate || '-' }}
259
+                </template>
260
+              </el-table-column>
261
+              <el-table-column prop="ironCont" label="含铁量%" width="100">
262
+                <template #default="scope">
263
+                  {{ scope.row.ironCont || '-' }}
264
+                </template>
265
+              </el-table-column>
266
+              <el-table-column prop="moisture" label="水分%" width="80">
267
+                <template #default="scope">
268
+                  {{ scope.row.moisture || '-' }}
269
+                </template>
270
+              </el-table-column>
271
+              <el-table-column prop="coalMoisture" label="煤焦计价水%" width="120">
272
+                <template #default="scope">
273
+                  {{ scope.row.coalMoisture || '-' }}
274
+                </template>
275
+              </el-table-column>
276
+              <el-table-column prop="priceMtd" label="计价方式" width="100">
277
+                <template #default="scope">
278
+                  {{ scope.row.priceMtd || '-' }}
279
+                </template>
280
+              </el-table-column>
281
+              <el-table-column prop="ratePo" label="采购汇率" width="100">
282
+                <template #default="scope">
283
+                  {{ scope.row.ratePo || '-' }}
284
+                </template>
285
+              </el-table-column>
286
+              <el-table-column prop="reTaxRate" label="退税率%" width="100">
287
+                <template #default="scope">
288
+                  {{ scope.row.reTaxRate || '-' }}
289
+                </template>
290
+              </el-table-column>
291
+              <el-table-column prop="ut" label="单位" width="80"></el-table-column>
292
+              <el-table-column prop="ftype4" label="计重方式" width="100">
293
+                <template #default="scope">
294
+                  {{ scope.row.ftype4 || '-' }}
295
+                </template>
296
+              </el-table-column>
297
+              <el-table-column prop="fnw" label="净重" width="100">
298
+                <template #default="scope">
299
+                  {{ formatNumber(scope.row.fnw) }}
300
+                </template>
301
+              </el-table-column>
302
+              <el-table-column prop="fgw" label="毛重" width="100">
303
+                <template #default="scope">
304
+                  {{ formatNumber(scope.row.fgw) }}
305
+                </template>
306
+              </el-table-column>
307
+              <el-table-column prop="taxAmt" label="税额" width="100">
308
+                <template #default="scope">
309
+                  {{ formatNumber(scope.row.taxAmt) }}
310
+                </template>
311
+              </el-table-column>
312
+              <el-table-column prop="spec" label="QP描述" width="150">
313
+                <template #default="scope">
314
+                  {{ scope.row.spec || '-' }}
315
+                </template>
316
+              </el-table-column>
317
+              <el-table-column prop="hsCode" label="海关编码" width="120">
318
+                <template #default="scope">
319
+                  {{ scope.row.hsCode || '-' }}
320
+                </template>
321
+              </el-table-column>
322
+              <el-table-column prop="hsNameCn" label="海关中文名称" width="150">
323
+                <template #default="scope">
324
+                  {{ scope.row.hsNameCn || '-' }}
325
+                </template>
326
+              </el-table-column>
327
+              <el-table-column prop="hsNameEn" label="海关英文名称" width="150">
328
+                <template #default="scope">
329
+                  {{ scope.row.hsNameEn || '-' }}
330
+                </template>
331
+              </el-table-column>
332
+              <el-table-column prop="dryTonQty" label="干吨数量" width="100">
333
+                <template #default="scope">
334
+                  {{ formatNumber(scope.row.dryTonQty) }}
335
+                </template>
336
+              </el-table-column>
337
+              <el-table-column prop="wetTonQty" label="湿吨数量" width="100">
338
+                <template #default="scope">
339
+                  {{ formatNumber(scope.row.wetTonQty) }}
340
+                </template>
341
+              </el-table-column>
342
+              <el-table-column prop="ftype5" label="色标" width="80">
343
+                <template #default="scope">
344
+                  {{ scope.row.ftype5 || '-' }}
345
+                </template>
346
+              </el-table-column>
347
+              <el-table-column prop="dutyRate" label="进口关税税率%" width="120">
348
+                <template #default="scope">
349
+                  {{ scope.row.dutyRate || '-' }}
350
+                </template>
351
+              </el-table-column>
352
+              <el-table-column prop="ntPoAmt" label="未税金额" width="100">
353
+                <template #default="scope">
354
+                  {{ formatNumber(scope.row.ntPoAmt) }}
355
+                </template>
356
+              </el-table-column>
357
+              <el-table-column prop="ntPoPrice" label="未税单价" width="100">
358
+                <template #default="scope">
359
+                  {{ formatNumber(scope.row.ntPoPrice) }}
360
+                </template>
361
+              </el-table-column>
362
+              <el-table-column prop="amtRate" label="金额占比" width="100">
363
+                <template #default="scope">
364
+                  {{ scope.row.amtRate || '-' }}
365
+                </template>
366
+              </el-table-column>
367
+              <el-table-column prop="cnypoamt" label="人民币含税金额" width="140">
368
+                <template #default="scope">
369
+                  {{ formatNumber(scope.row.cnypoamt) }}
370
+                </template>
371
+              </el-table-column>
372
+              <el-table-column prop="usdpoamt" label="美元含税金额" width="140">
373
+                <template #default="scope">
374
+                  {{ formatNumber(scope.row.usdpoamt) }}
375
+                </template>
376
+              </el-table-column>
377
+              <el-table-column prop="remark" label="备注" width="150">
378
+                <template #default="scope">
379
+                  <el-tooltip :content="scope.row.remark" placement="top" v-if="scope.row.remark">
380
+                    <span class="text-ellipsis">{{ scope.row.remark }}</span>
381
+                  </el-tooltip>
382
+                  <span v-else>-</span>
383
+                </template>
384
+              </el-table-column>
385
+            </el-table>
386
+            <div class="pagination-section" v-if="allProducts.length > 0">
387
+              <el-pagination
388
+                :total="allProducts.length"
389
+                :page-size="productPageSize"
390
+                :current-page="productCurrentPage"
391
+                @current-change="handleProductPageChange"
392
+                layout="total, prev, pager, next, jumper"
393
+              />
394
+            </div>
395
+          </div>
396
+        </el-tab-pane>
397
+        
398
+        <!-- 付款计划 -->
399
+        <el-tab-pane label="付款计划" name="payments">
400
+          <div class="table-section">
401
+            <el-table :data="allPayments" style="width: 100%" border height="500">
402
+              <el-table-column prop="fno" label="采购合同号" width="150" fixed="left"></el-table-column>
403
+              <el-table-column prop="create_by" label="制单人代码" width="120"></el-table-column>
404
+              <el-table-column prop="dept_name" label="部门名称" width="150"></el-table-column>
405
+              <el-table-column prop="payMethod" label="付款方式" width="120"></el-table-column>
406
+              <el-table-column prop="payType" label="款项类别" width="120"></el-table-column>
407
+              <el-table-column prop="payDays" label="账期(天)" width="100">
408
+                <template #default="scope">
409
+                  {{ scope.row.payDays || '0' }}
410
+                </template>
411
+              </el-table-column>
412
+              <el-table-column prop="payRatio" label="款项比例%" width="120">
413
+                <template #default="scope">
414
+                  {{ scope.row.payRatio || '0' }}
415
+                </template>
416
+              </el-table-column>
417
+              <el-table-column prop="payAmt" label="金额" width="120">
418
+                <template #default="scope">
419
+                  {{ formatNumber(scope.row.payAmt) }}
420
+                </template>
421
+              </el-table-column>
422
+            </el-table>
423
+          </div>
424
+        </el-tab-pane>
425
+        
426
+        <!-- 责任中心 -->
427
+        <el-tab-pane label="责任中心" name="responsibility">
428
+          <div class="table-section">
429
+            <el-table :data="allResponsibilities" style="width: 100%" border height="500">
430
+              <el-table-column prop="fno" label="采购合同号" width="150" fixed="left"></el-table-column>
431
+              <el-table-column prop="create_by" label="制单人代码" width="120"></el-table-column>
432
+              <el-table-column prop="personDeptNm" label="部门名称" width="150"></el-table-column>
433
+              <el-table-column prop="assessQtyRate" label="利润考核占比%" width="120">
434
+                <template #default="scope">
435
+                  {{ scope.row.assessQtyRate || '0' }}
436
+                </template>
437
+              </el-table-column>
438
+              <el-table-column prop="assessRatio" label="金额考核占比%" width="120">
439
+                <template #default="scope">
440
+                  {{ scope.row.assessRatio || '0' }}
441
+                </template>
442
+              </el-table-column>
443
+              <el-table-column prop="InstitutionId" label="公司代码" width="120">
444
+                <template #default="scope">
445
+                  {{ scope.row.InstitutionId || '-' }}
446
+                </template>
447
+              </el-table-column>
448
+              <el-table-column prop="InstitutionNm" label="公司名称" width="150">
449
+                <template #default="scope">
450
+                  {{ scope.row.InstitutionNm || '-' }}
451
+                </template>
452
+              </el-table-column>
453
+              <el-table-column prop="personDeptId" label="部门代码" width="120">
454
+                <template #default="scope">
455
+                  {{ scope.row.personDeptId || '-' }}
456
+                </template>
457
+              </el-table-column>
458
+              <el-table-column prop="personid" label="人员代码" width="120">
459
+                <template #default="scope">
460
+                  {{ scope.row.personid || '-' }}
461
+                </template>
462
+              </el-table-column>
463
+              <el-table-column prop="personname" label="人员名称" width="150">
464
+                <template #default="scope">
465
+                  {{ scope.row.personname || '-' }}
466
+                </template>
467
+              </el-table-column>
468
+            </el-table>
469
+          </div>
470
+        </el-tab-pane>
471
+      </el-tabs>
472
+    </div>
473
+  </div>
474
+</template>
475
+
476
+<script setup>
477
+import { ref, onMounted, computed } from 'vue'
478
+import { ElMessage, ElMessageBox } from 'element-plus'
479
+import { UploadFilled } from '@element-plus/icons-vue'
480
+import { importContractExcel } from '../api/contractApi'
481
+import * as XLSX from 'xlsx'
482
+
483
+// 响应式数据
484
+const selectedFile = ref(null)
485
+const contractsData = ref({
486
+  main: [],
487
+  products: [],
488
+  payments: [],
489
+  responsibility: []
490
+})
491
+const activeTab = ref('main')
492
+const importLoading = ref(false)
493
+const saveLoading = ref(false)
494
+const tableLoading = ref(false)
495
+const importStats = ref(null)
496
+const currentPage = ref(1)
497
+const pageSize = ref(50)
498
+const productCurrentPage = ref(1)
499
+const productPageSize = ref(50)
500
+
501
+// 计算分页后的合同数据
502
+const paginatedContracts = computed(() => {
503
+  if (!contractsData.value.main || contractsData.value.main.length === 0) return []
504
+  const start = (currentPage.value - 1) * pageSize.value
505
+  const end = start + pageSize.value
506
+  return contractsData.value.main.slice(start, end)
507
+})
508
+
509
+// 计算所有产品的数据
510
+const allProducts = computed(() => {
511
+  return contractsData.value.products || []
512
+})
513
+
514
+// 计算分页后的产品数据
515
+const paginatedProducts = computed(() => {
516
+  const start = (productCurrentPage.value - 1) * productPageSize.value
517
+  const end = start + productPageSize.value
518
+  return allProducts.value.slice(start, end)
519
+})
520
+
521
+// 计算所有付款计划
522
+const allPayments = computed(() => {
523
+  return contractsData.value.payments || []
524
+})
525
+
526
+// 计算所有责任中心
527
+const allResponsibilities = computed(() => {
528
+  return contractsData.value.responsibility || []
529
+})
530
+
531
+// 处理文件选择
532
+const handleFileChange = (file) => {
533
+  selectedFile.value = file.raw
534
+}
535
+
536
+// 格式化数字显示
537
+const formatNumber = (value) => {
538
+  if (value === null || value === undefined || value === '') return '-'
539
+  const num = Number(value)
540
+  return isNaN(num) ? value : num.toLocaleString()
541
+}
542
+
543
+// 解析Excel文件并预览内容
544
+const previewFileContent = async (file) => {
545
+  try {
546
+    tableLoading.value = true
547
+    const data = await parseExcelFile(file);
548
+    contractsData.value = data;
549
+    
550
+    console.log('解析完成的数据:', data)
551
+    
552
+    // 计算统计信息
553
+    importStats.value = {
554
+      contractCount: data.main ? data.main.length : 0,
555
+      productsCount: data.products ? data.products.length : 0,
556
+      paymentsCount: data.payments ? data.payments.length : 0,
557
+      responsibilityCount: data.responsibility ? data.responsibility.length : 0
558
+    }
559
+    
560
+    ElMessage.success(`成功导入 ${importStats.value.contractCount} 个合同,共 ${importStats.value.productsCount} 条产品记录`)
561
+  } catch (error) {
562
+    console.error('文件解析错误:', error)
563
+    ElMessage.error('文件解析失败:' + (error.message || '未知错误'));
564
+    contractsData.value = {
565
+      main: [],
566
+      products: [],
567
+      payments: [],
568
+      responsibility: []
569
+    }
570
+  } finally {
571
+    tableLoading.value = false
572
+  }
573
+};
574
+
575
+// 解析Excel文件 - 只做数据收集,不进行关联
576
+const parseExcelFile = (file) => {
577
+  return new Promise((resolve, reject) => {
578
+    const reader = new FileReader();
579
+    
580
+    reader.onload = (e) => {
581
+      try {
582
+        const data = new Uint8Array(e.target.result);
583
+        const workbook = XLSX.read(data, { 
584
+          type: 'array',
585
+          cellDates: true,
586
+          raw: false
587
+        });
588
+        
589
+        console.log('Excel Sheet Names:', workbook.SheetNames)
590
+        
591
+        const result = {
592
+          main: [],
593
+          products: [],
594
+          payments: [],
595
+          responsibility: []
596
+        }
597
+        
598
+        // 解析主表(第一个sheet)
599
+        const mainSheet = workbook.Sheets[workbook.SheetNames[0]];
600
+        const mainData = XLSX.utils.sheet_to_json(mainSheet, { 
601
+          header: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ', 'BA', 'BB', 'BC', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BK', 'BL', 'BM', 'BN', 'BO', 'BP', 'BQ', 'BR', 'BS', 'BT', 'BU', 'BV', 'BW', 'BX', 'BY', 'BZ', 'CA', 'CB', 'CC', 'CD', 'CE', 'CF', 'CG', 'CH', 'CI', 'CJ', 'CK', 'CL', 'CM', 'CN', 'CO', 'CP', 'CQ', 'CR', 'CS', 'CT', 'CU', 'CV'],
602
+          range: 1
603
+        });
604
+        
605
+        console.log('主表数据:', mainData)
606
+        
607
+        // 处理主表数据
608
+        mainData.forEach((row, index) => {
609
+          // 跳过空行和标题行
610
+          if (!row.B || row.B === '采购合同号' || row.B === '标题') return;
611
+          
612
+          const mainInfo = mapMainTableFields(row);
613
+          result.main.push(mainInfo);
614
+        });
615
+        
616
+        // 解析产品明细(第二个sheet)
617
+        if (workbook.SheetNames.length >= 2) {
618
+          const productsSheet = workbook.Sheets[workbook.SheetNames[1]];
619
+          const productsData = XLSX.utils.sheet_to_json(productsSheet, { 
620
+            header: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ', 'BA', 'BB', 'BC', 'BD'],
621
+            range: 1
622
+          });
623
+          
624
+          console.log('产品明细数据:', productsData)
625
+          
626
+          // 处理产品明细数据
627
+          productsData.forEach(row => {
628
+            if (!row.A || row.A === '采购合同号' || row.A === '标题') return;
629
+            
630
+            const productItem = mapProductTableFields(row);
631
+            result.products.push(productItem);
632
+          });
633
+        }
634
+        
635
+        // 解析付款计划(第三个sheet)
636
+        if (workbook.SheetNames.length >= 3) {
637
+          const paymentsSheet = workbook.Sheets[workbook.SheetNames[2]];
638
+          const paymentsData = XLSX.utils.sheet_to_json(paymentsSheet, { 
639
+            header: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],
640
+            range: 1
641
+          });
642
+          
643
+          console.log('付款计划数据:', paymentsData)
644
+          
645
+          // 处理付款计划数据
646
+          paymentsData.forEach(row => {
647
+            if (!row.A || row.A === '合同号' || row.A === '标题') return;
648
+            
649
+            const paymentItem = mapPaymentTableFields(row);
650
+            result.payments.push(paymentItem);
651
+          });
652
+        }
653
+        
654
+        // 解析责任中心(第四个sheet)
655
+        if (workbook.SheetNames.length >= 4) {
656
+          const responsibilitySheet = workbook.Sheets[workbook.SheetNames[3]];
657
+          const responsibilityData = XLSX.utils.sheet_to_json(responsibilitySheet, { 
658
+            header: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'],
659
+            range: 1
660
+          });
661
+          
662
+          console.log('责任中心数据:', responsibilityData)
663
+          
664
+          // 处理责任中心数据
665
+          responsibilityData.forEach(row => {
666
+            if (!row.A || row.A === '合同号' || row.A === '标题') return;
667
+            
668
+            const respItem = mapResponsibilityTableFields(row);
669
+            result.responsibility.push(respItem);
670
+          });
671
+        }
672
+        
673
+        console.log('最终解析的数据:', result)
674
+        resolve(result);
675
+      } catch (error) {
676
+        console.error('解析过程中出错:', error)
677
+        reject(error);
678
+      }
679
+    };
680
+    
681
+    reader.onerror = () => {
682
+      reject(new Error('文件读取失败'));
683
+    };
684
+    
685
+    reader.readAsArrayBuffer(file);
686
+  });
687
+};
688
+
689
+// 主表字段映射 - 根据Excel结构调整
690
+const mapMainTableFields = (row) => {
691
+  return {
692
+    fno: row.A || '', // 采购合同号 (A列)
693
+    contractName: row.W || '', // 合同名称 (W列)
694
+    bizType: row.G || '', // 合同类型 (G列)
695
+    bizProductType: row.H || '', // 合同产品类型 (H列)
696
+    serviceType: row.F || '', // 业务类型 (F列)
697
+    curcy: row.AA || '', // 币别 (AA列)
698
+    supName: row.CM || '', // 供应商名称 (CM列)
699
+    supCode: row.CK || '', // 供应商代码 (CK列)
700
+    supContact: row.CL || '', // 供应商联系人名称 (CL列)
701
+    supBank: row.CJ || '', // 供应商开户银行 (CJ列)
702
+    supAcc: row.CI || '', // 供应商银行账户 (CI列)
703
+    fours: row.I || '', // 我方编号 (I列)
704
+    foursname: row.J || '', // 我方名称 (J列)
705
+    ourBankNm: row.AQ || '', // 我方开户银行 (AQ列)
706
+    ourBankAcct: row.AP || '', // 我方银行账号 (AP列)
707
+    createBy: row.C || '', // 制单人代码 (C列)
708
+    createByName: row.D || '', // 制单人名称 (D列)
709
+    contrExecCd: row.E || '', // 合同执行人代码 (E列)
710
+    contrExecNm: row.U || '', // 合同执行人名称 (U列)
711
+    ifLongTerm: row.AX || '', // 是否长协 (AX列)
712
+    ifClearBottom: row.AT || '', // 是否清底 (AT列)
713
+    fundOccupyFlg: row.AR || '', // 是否占用资金 (AR列)
714
+    ifBidding: row.AS || '', // 是否招投标 (AS列)
715
+    lPort: row.BF || '', // 装运港 (BF列)
716
+    lPortCtry: row.BG || '', // 装运国 (BG列)
717
+    dPort: row.AF || '', // 目的港 (AF列)
718
+    dPortCtry: row.AG || '', // 目的国 (AG列)
719
+    tranWay: row.CQ || '', // 运输方式 (CQ列)
720
+    delivMtd: row.AE || '', // 交货方式 (AE列)
721
+    delivLoc: row.AD || '', // 交货地点 (AD列)
722
+    estArrDt: row.AL || '', // 最迟集港日期 (AL列)
723
+    estLoadDt: row.AM || '', // 最迟装运日期 (AM列)
724
+    whetherChartering: row.CT || '', // 是否租船 (CT列)
725
+    chnShipName: row.R || '', // 中文船名 (R列)
726
+    engShipName: row.AK || '', // 英文船名 (AK列)
727
+    terms: row.CN || '', // 价格条款 (CN列)
728
+    rate: row.BY || 0, // 折人民币汇率 (BY列)
729
+    rateUs: row.BZ || 0, // 执行美元汇率 (BZ列)
730
+    qtyOver: row.BW || 0, // 数量溢装% (BW列)
731
+    qtyShort: row.BX || 0, // 数量短装% (BX列)
732
+    amtOver: row.K || 0, // 金额溢装% (K列)
733
+    amtShort: row.L || 0, // 金额短装% (L列)
734
+    insuranceType: row.BD || '', // 保险种类 (BD列)
735
+    insuranceAmt: row.BE || 0, // 保险费率% (BE列)
736
+    signLoc: row.CG || '', // 签约地点 (CG列)
737
+    contrDesc: row.AB || '', // 合同描述 (AB列)
738
+  };
739
+};
740
+
741
+// 产品明细字段映射 - 根据Excel结构调整
742
+const mapProductTableFields = (row) => {
743
+  return {
744
+    fno: row.A || '', // 采购合同号 (A列)
745
+    itemno: row.F || '', // 商品编号 (F列)
746
+    sdesc: row.G || '', // 中文品名 (G列)
747
+    edesc: row.H || '', // 英文品名 (H列)
748
+    brand: row.I || '', // 牌号 (I列)
749
+    ftype3: row.J || '', // 规格描述 (J列)
750
+    resourceNo: row.C || '', // 资源号 (C列)
751
+    SMRNo: row.D || '', // 钢厂资源号 (D列)
752
+    batchNo: row.E || '', // 批次号 (E列)
753
+    curcyPo: row.AL || '', // 采购币种 (AL列)
754
+    qty: row.S || 0, // 数量 (S列)
755
+    poprice: row.T || 0, // 采购单价 (T列)
756
+    poamt: row.U || 0, // 金额 (U列)
757
+    inTaxRate: row.AG || 0, // 增值税率% (AG列)
758
+    ironCont: row.W || 0, // 含铁量% (W列)
759
+    moisture: row.V || 0, // 水分% (V列)
760
+    coalMoisture: row.AM || 0, // 煤焦计价水% (AM列)
761
+    priceMtd: row.AC || '', // 计价方式 (AC列)
762
+    ratePo: row.AX || 0, // 采购汇率 (AX列)
763
+    reTaxRate: row.AY || 0, // 退税率% (AY列)
764
+    ut: row.R || '', // 单位 (R列)
765
+    ftype4: row.AB || '', // 计重方式 (AB列)
766
+    fnw: row.AE || 0, // 净重 (AE列)
767
+    fgw: row.AD || 0, // 毛重 (AD列)
768
+    taxAmt: row.BC || 0, // 税额 (BC列)
769
+    spec: row.AH || '', // QP描述 (AH列)
770
+    hsCode: row.AI || '', // 海关编码 (AI列)
771
+    hsNameCn: row.AJ || '', // 海关中文名称 (AJ列)
772
+    hsNameEn: row.AK || '', // 海关英文名称 (AK列)
773
+    dryTonQty: row.Y || 0, // 干吨数量 (Y列)
774
+    wetTonQty: row.Z || 0, // 湿吨数量 (Z列)
775
+    ftype5: row.AA || '', // 色标 (AA列)
776
+    dutyRate: row.AF || 0, // 进口关税税率% (AF列)
777
+    ntPoAmt: row.AN || 0, // 未税金额 (AN列)
778
+    ntPoPrice: row.AO || 0, // 未税单价 (AO列)
779
+    amtRate: row.AP || 0, // 金额占比 (AP列)
780
+    cnypoamt: row.AQ || 0, // 人民币含税金额 (AQ列)
781
+    usdpoamt: row.BD || 0, // 美元含税金额 (BD列)
782
+    remark: row.AR || '' // 备注 (AR列)
783
+  };
784
+};
785
+
786
+// 付款计划字段映射 - 根据Excel结构调整
787
+const mapPaymentTableFields = (row) => {
788
+  return {
789
+    fno: row.A || '', // 合同号 (A列)
790
+    create_by: row.B || '', // 制单人代码 (B列)
791
+    dept_name: row.C || '', // 部门名称 (C列)
792
+    payMethod: row.D || '', // 付款方式 (D列)
793
+    payType: row.E || '', // 款项类别 (E列)
794
+    payDays: row.F || 0, // 账期(天) (F列)
795
+    payRatio: row.G || 0, // 款项比例% (G列)
796
+    payAmt: row.H || 0 // 金额 (H列)
797
+  };
798
+};
799
+
800
+// 责任中心字段映射 - 根据Excel结构调整
801
+const mapResponsibilityTableFields = (row) => {
802
+  return {
803
+    fno: row.A || '', // 合同号 (A列)
804
+    create_by: row.B || '', // 制单人代码 (B列)
805
+    personDeptNm: row.C || '', // 部门名称 (C列)
806
+    assessQtyRate: row.D || 0, // 利润考核占比% (D列)
807
+    assessRatio: row.E || 0, // 金额考核占比% (E列)
808
+    InstitutionId: row.F || '', // 人员公司代码 (F列)
809
+    InstitutionNm: row.G || '', // 人员公司名称 (G列)
810
+    personDeptId: row.H || '', // 人员部门代码 (H列)
811
+    personid: row.I || '', // 人员代码 (I列)
812
+    personname: row.J || '' // 人员名称 (J列)
813
+  };
814
+};
815
+
816
+// 处理文件导入
817
+const handleImport = async () => {
818
+  if (!selectedFile.value) {
819
+    ElMessage.warning('请先选择文件');
820
+    return;
821
+  }
822
+  
823
+  try {
824
+    importLoading.value = true;
825
+    ElMessage.info('正在解析文件,请稍候...');
826
+    
827
+    await previewFileContent(selectedFile.value);
828
+    
829
+    ElMessage.success('文件解析完成');
830
+  } catch (error) {
831
+    ElMessage.error('导入失败:' + (error.message || '未知错误'));
832
+  } finally {
833
+    importLoading.value = false;
834
+  }
835
+};
836
+
837
+// 分页处理
838
+const handlePageChange = (page) => {
839
+  currentPage.value = page;
840
+};
841
+
842
+const handleProductPageChange = (page) => {
843
+  productCurrentPage.value = page;
844
+};
845
+
846
+// 下载模板
847
+const handleDownloadTemplate = () => {
848
+  ElMessage.info('模板下载功能开发中...');
849
+};
850
+
851
+// 批量保存所有合同
852
+const handleBatchSave = async () => {
853
+  if (!contractsData.value || contractsData.value.main.length === 0) {
854
+    ElMessage.warning('请先导入合同数据');
855
+    return;
856
+  }
857
+  
858
+  try {
859
+    await ElMessageBox.confirm(
860
+      `确定要批量保存 ${importStats.value.contractCount} 个合同吗?`,
861
+      '批量保存确认',
862
+      {
863
+        confirmButtonText: '确定',
864
+        cancelButtonText: '取消',
865
+        type: 'warning'
866
+      }
867
+    );
868
+    
869
+    saveLoading.value = true;
870
+    ElMessage.info('开始批量保存,请勿关闭页面...');
871
+    
872
+    try {
873
+      await importContractExcel(contractsData.value);
874
+      ElMessage.success(`成功保存所有 ${importStats.value.contractCount} 个合同`);
875
+    } catch (error) {
876
+      console.error('保存失败:', error);
877
+      ElMessage.error('批量保存失败:' + (error.message || '未知错误'));
878
+    }
879
+  } catch (error) {
880
+    if (error !== 'cancel') {
881
+      ElMessage.error('批量保存失败:' + (error.message || '未知错误'));
882
+    }
883
+  } finally {
884
+    saveLoading.value = false;
885
+  }
886
+};
887
+</script>
888
+
889
+<style scoped>
890
+.contract-container {
891
+  padding: 20px;
892
+}
893
+
894
+.import-section {
895
+  margin-bottom: 30px;
896
+  padding: 20px;
897
+  background-color: #f5f7fa;
898
+  border-radius: 8px;
899
+}
900
+
901
+.el-upload {
902
+  margin-bottom: 20px;
903
+}
904
+
905
+.import-actions {
906
+  margin-top: 20px;
907
+  display: flex;
908
+  gap: 10px;
909
+}
910
+
911
+.import-stats {
912
+  margin-top: 20px;
913
+}
914
+
915
+.stats-content {
916
+  display: flex;
917
+  gap: 20px;
918
+  flex-wrap: wrap;
919
+}
920
+
921
+.contracts-info {
922
+  margin-top: 30px;
923
+}
924
+
925
+.table-section {
926
+  padding: 20px;
927
+  background-color: #fff;
928
+}
929
+
930
+.pagination-section {
931
+  margin-top: 20px;
932
+  text-align: center;
933
+}
934
+
935
+.text-ellipsis {
936
+  display: inline-block;
937
+  max-width: 180px;
938
+  overflow: hidden;
939
+  text-overflow: ellipsis;
940
+  white-space: nowrap;
941
+  vertical-align: middle;
942
+}
943
+</style>

+ 994
- 0
src/views/SalesContractView.vue Datei anzeigen

@@ -0,0 +1,994 @@
1
+<template>
2
+  <div class="contract-container">
3
+    <h1>销售合同管理</h1>
4
+    
5
+    <!-- 文件导入区域 -->
6
+    <div class="import-section">
7
+      <el-upload
8
+        class="upload-demo"
9
+        action=""
10
+        :auto-upload="false"
11
+        :on-change="handleFileChange"
12
+        :show-file-list="true"
13
+        accept=".xlsx,.xls"
14
+        drag
15
+      >
16
+        <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
17
+        <div class="el-upload__text">
18
+          拖放文件到此处,或 <em>点击上传</em>
19
+        </div>
20
+        <template #tip>
21
+          <div class="el-upload__tip">
22
+            请上传符合模板格式的Excel文件(.xlsx/.xls),支持"销售合同导入模板2.0.xlsx"格式
23
+          </div>
24
+        </template>
25
+      </el-upload>
26
+      
27
+      <el-button type="primary" @click="handleImport" :disabled="!selectedFile">导入文件</el-button>
28
+    </div>
29
+    
30
+    <!-- 合同信息展示区域 -->
31
+    <div class="contract-info" v-if="contractData">
32
+      <el-tabs v-model="activeTab" type="border-card">
33
+        <!-- 主表信息(第一个sheet) -->
34
+        <el-tab-pane label="合同基本信息" name="main">
35
+          <div class="form-section">
36
+            <el-form :model="contractData.main" label-width="120px" class="demo-ruleForm">
37
+              <!-- 合同基本信息 -->
38
+              <h3 style="margin-bottom: 15px; padding-left: 10px; border-left: 4px solid #409EFF;">合同基本信息</h3>
39
+              <el-row :gutter="20">
40
+                <el-col :span="8">
41
+                  <el-form-item label="采购合同号">
42
+                    <el-input v-model="contractData.main.poNo" disabled></el-input>
43
+                  </el-form-item>
44
+                </el-col>
45
+                <el-col :span="8">
46
+                  <el-form-item label="销售合同号">
47
+                    <el-input v-model="contractData.main.contractCode" disabled></el-input>
48
+                  </el-form-item>
49
+                </el-col>
50
+                <el-col :span="8">
51
+                  <el-form-item label="合同类型">
52
+                    <el-input v-model="contractData.main.contractType" disabled></el-input>
53
+                  </el-form-item>
54
+                </el-col>
55
+              </el-row>
56
+              <el-row :gutter="20">
57
+                <el-col :span="8">
58
+                  <el-form-item label="合同产品类型">
59
+                    <el-input v-model="contractData.main.contractProductType" disabled></el-input>
60
+                  </el-form-item>
61
+                </el-col>
62
+                <el-col :span="8">
63
+                  <el-form-item label="业务类型">
64
+                    <el-input v-model="contractData.main.serviceType" disabled></el-input>
65
+                  </el-form-item>
66
+                </el-col>
67
+                <el-col :span="8">
68
+                  <el-form-item label="合同状态">
69
+                    <el-input v-model="contractData.main.contractStatus" disabled></el-input>
70
+                  </el-form-item>
71
+                </el-col>
72
+              </el-row>
73
+              <el-row :gutter="20">
74
+                <el-col :span="8">
75
+                  <el-form-item label="签约地点">
76
+                    <el-input v-model="contractData.main.signLoc" disabled></el-input>
77
+                  </el-form-item>
78
+                </el-col>
79
+                <el-col :span="8">
80
+                  <el-form-item label="币别">
81
+                    <el-input v-model="contractData.main.curcy" disabled></el-input>
82
+                  </el-form-item>
83
+                </el-col>
84
+                <el-col :span="8">
85
+                  <el-form-item label="折人民币汇率">
86
+                    <el-input v-model="contractData.main.rate" disabled></el-input>
87
+                  </el-form-item>
88
+                </el-col>
89
+              </el-row>
90
+              <el-row :gutter="20">
91
+                <el-col :span="8">
92
+                  <el-form-item label="执行美元汇率">
93
+                    <el-input v-model="contractData.main.rateUs" disabled></el-input>
94
+                  </el-form-item>
95
+                </el-col>
96
+                <el-col :span="8">
97
+                  <el-form-item label="价格条款">
98
+                    <el-input v-model="contractData.main.terms" disabled></el-input>
99
+                  </el-form-item>
100
+                </el-col>
101
+                <el-col :span="8">
102
+                  <el-form-item label="装卸条款">
103
+                    <el-input v-model="contractData.main.loadUnloadTerms" disabled></el-input>
104
+                  </el-form-item>
105
+                </el-col>
106
+              </el-row>
107
+              
108
+              <!-- 客户信息 -->
109
+              <h3 style="margin-bottom: 15px; margin-top: 25px; padding-left: 10px; border-left: 4px solid #67C23A;">客户信息</h3>
110
+              <el-row :gutter="20">
111
+                <el-col :span="8">
112
+                  <el-form-item label="客户名称">
113
+                    <el-input v-model="contractData.main.customerName" disabled></el-input>
114
+                  </el-form-item>
115
+                </el-col>
116
+                <el-col :span="8">
117
+                  <el-form-item label="客户代码">
118
+                    <el-input v-model="contractData.main.customerCode" disabled></el-input>
119
+                  </el-form-item>
120
+                </el-col>
121
+                <el-col :span="8">
122
+                  <el-form-item label="客户联系人名称">
123
+                    <el-input v-model="contractData.main.ctContNm" disabled></el-input>
124
+                  </el-form-item>
125
+                </el-col>
126
+              </el-row>
127
+              <el-row :gutter="20">
128
+                <el-col :span="8">
129
+                  <el-form-item label="客户开户银行">
130
+                    <el-input v-model="contractData.main.ctBankName" disabled></el-input>
131
+                  </el-form-item>
132
+                </el-col>
133
+                <el-col :span="8">
134
+                  <el-form-item label="客户银行账户">
135
+                    <el-input v-model="contractData.main.ctBankAcct" disabled></el-input>
136
+                  </el-form-item>
137
+                </el-col>
138
+                <el-col :span="8">
139
+                  <el-form-item label="客户合同号">
140
+                    <el-input v-model="contractData.main.ctOrderNo" disabled></el-input>
141
+                  </el-form-item>
142
+                </el-col>
143
+              </el-row>
144
+              
145
+              <!-- 我方信息 -->
146
+              <h3 style="margin-bottom: 15px; margin-top: 25px; padding-left: 10px; border-left: 4px solid #E6A23C;">我方信息</h3>
147
+              <el-row :gutter="20">
148
+                <el-col :span="8">
149
+                  <el-form-item label="我方编号">
150
+                    <el-input v-model="contractData.main.fours" disabled></el-input>
151
+                  </el-form-item>
152
+                </el-col>
153
+                <el-col :span="8">
154
+                  <el-form-item label="我方名称">
155
+                    <el-input v-model="contractData.main.foursname" disabled></el-input>
156
+                  </el-form-item>
157
+                </el-col>
158
+                <el-col :span="8">
159
+                  <el-form-item label="我方开户银行">
160
+                    <el-input v-model="contractData.main.ourBankNm" disabled></el-input>
161
+                  </el-form-item>
162
+                </el-col>
163
+              </el-row>
164
+              <el-row :gutter="20">
165
+                <el-col :span="8">
166
+                  <el-form-item label="我方银行账号">
167
+                    <el-input v-model="contractData.main.ourBankAcct" disabled></el-input>
168
+                  </el-form-item>
169
+                </el-col>
170
+                <el-col :span="8">
171
+                  <el-form-item label="制单人代码">
172
+                    <el-input v-model="contractData.main.createBy" disabled></el-input>
173
+                  </el-form-item>
174
+                </el-col>
175
+                <el-col :span="8">
176
+                  <el-form-item label="制单人名称">
177
+                    <el-input v-model="contractData.main.createByName" disabled></el-input>
178
+                  </el-form-item>
179
+                </el-col>
180
+              </el-row>
181
+              
182
+              <!-- 合同执行信息 -->
183
+              <h3 style="margin-bottom: 15px; margin-top: 25px; padding-left: 10px; border-left: 4px solid #909399;">合同执行信息</h3>
184
+              <el-row :gutter="20">
185
+                <el-col :span="8">
186
+                  <el-form-item label="合同执行人代码">
187
+                    <el-input v-model="contractData.main.contrExecCd" disabled></el-input>
188
+                  </el-form-item>
189
+                </el-col>
190
+                <el-col :span="8">
191
+                  <el-form-item label="合同执行人名称">
192
+                    <el-input v-model="contractData.main.contrExecNm" disabled></el-input>
193
+                  </el-form-item>
194
+                </el-col>
195
+                <el-col :span="8">
196
+                  <el-form-item label="是否长协">
197
+                    <el-input v-model="contractData.main.ifLongTerm" disabled></el-input>
198
+                  </el-form-item>
199
+                </el-col>
200
+              </el-row>
201
+              <el-row :gutter="20">
202
+                <el-col :span="8">
203
+                  <el-form-item label="长协合同号">
204
+                    <el-input v-model="contractData.main.longTermContract" disabled></el-input>
205
+                  </el-form-item>
206
+                </el-col>
207
+                <el-col :span="8">
208
+                  <el-form-item label="是否清底">
209
+                    <el-input v-model="contractData.main.ifClearBottom" disabled></el-input>
210
+                  </el-form-item>
211
+                </el-col>
212
+                <el-col :span="8">
213
+                  <el-form-item label="预计客商占款天数">
214
+                    <el-input v-model="contractData.main.outstandingDay" disabled></el-input>
215
+                  </el-form-item>
216
+                </el-col>
217
+              </el-row>
218
+              
219
+              <!-- 物流信息 -->
220
+              <h3 style="margin-bottom: 15px; margin-top: 25px; padding-left: 10px; border-left: 4px solid #F56C6C;">物流信息</h3>
221
+              <el-row :gutter="20">
222
+                <el-col :span="8">
223
+                  <el-form-item label="装运港">
224
+                    <el-input v-model="contractData.main.lPort" disabled></el-input>
225
+                  </el-form-item>
226
+                </el-col>
227
+                <el-col :span="8">
228
+                  <el-form-item label="装运国">
229
+                    <el-input v-model="contractData.main.lPortCtry" disabled></el-input>
230
+                  </el-form-item>
231
+                </el-col>
232
+                <el-col :span="8">
233
+                  <el-form-item label="目的港">
234
+                    <el-input v-model="contractData.main.dPort" disabled></el-input>
235
+                  </el-form-item>
236
+                </el-col>
237
+              </el-row>
238
+              <el-row :gutter="20">
239
+                <el-col :span="8">
240
+                  <el-form-item label="目的国">
241
+                    <el-input v-model="contractData.main.dPortCtry" disabled></el-input>
242
+                  </el-form-item>
243
+                </el-col>
244
+                <el-col :span="8">
245
+                  <el-form-item label="运输方式">
246
+                    <el-input v-model="contractData.main.tranWay" disabled></el-input>
247
+                  </el-form-item>
248
+                </el-col>
249
+                <el-col :span="8">
250
+                  <el-form-item label="交货方式">
251
+                    <el-input v-model="contractData.main.delivMtd" disabled></el-input>
252
+                  </el-form-item>
253
+                </el-col>
254
+              </el-row>
255
+              <el-row :gutter="20">
256
+                <el-col :span="8">
257
+                  <el-form-item label="交货地点">
258
+                    <el-input v-model="contractData.main.delivLoc" disabled></el-input>
259
+                  </el-form-item>
260
+                </el-col>
261
+                <el-col :span="8">
262
+                  <el-form-item label="销售交货日期">
263
+                    <el-input v-model="contractData.main.sDelivDt" disabled></el-input>
264
+                  </el-form-item>
265
+                </el-col>
266
+                <el-col :span="8">
267
+                  <el-form-item label="最迟集港日期">
268
+                    <el-input v-model="contractData.main.estArrDt" disabled></el-input>
269
+                  </el-form-item>
270
+                </el-col>
271
+              </el-row>
272
+              <el-row :gutter="20">
273
+                <el-col :span="8">
274
+                  <el-form-item label="最迟装运日期">
275
+                    <el-input v-model="contractData.main.estLoadDt" disabled></el-input>
276
+                  </el-form-item>
277
+                </el-col>
278
+                <el-col :span="8">
279
+                  <el-form-item label="是否租船">
280
+                    <el-input v-model="contractData.main.whetherChartering" disabled></el-input>
281
+                  </el-form-item>
282
+                </el-col>
283
+                <el-col :span="8">
284
+                  <el-form-item label="中文船名">
285
+                    <el-input v-model="contractData.main.chnShipName" disabled></el-input>
286
+                  </el-form-item>
287
+                </el-col>
288
+              </el-row>
289
+              <el-row :gutter="20">
290
+                <el-col :span="8">
291
+                  <el-form-item label="英文船名">
292
+                    <el-input v-model="contractData.main.engShipName" disabled></el-input>
293
+                  </el-form-item>
294
+                </el-col>
295
+                <el-col :span="8">
296
+                  <el-form-item label="代理采购协议号">
297
+                    <el-input v-model="contractData.main.agentNo" disabled></el-input>
298
+                  </el-form-item>
299
+                </el-col>
300
+                <el-col :span="8">
301
+                  <el-form-item label="代理销售协议号">
302
+                    <el-input v-model="contractData.main.agentNoSo" disabled></el-input>
303
+                  </el-form-item>
304
+                </el-col>
305
+              </el-row>
306
+              
307
+              <!-- 其他信息 -->
308
+              <h3 style="margin-bottom: 15px; margin-top: 25px; padding-left: 10px; border-left: 4px solid #C0C4CC;">其他信息</h3>
309
+              <el-row :gutter="20">
310
+                <el-col :span="8">
311
+                  <el-form-item label="佣金合同号">
312
+                    <el-input v-model="contractData.main.commContrNo" disabled></el-input>
313
+                  </el-form-item>
314
+                </el-col>
315
+                <el-col :span="8">
316
+                  <el-form-item label="数量溢装%">
317
+                    <el-input v-model="contractData.main.qtyOver" disabled></el-input>
318
+                  </el-form-item>
319
+                </el-col>
320
+                <el-col :span="8">
321
+                  <el-form-item label="数量短装%">
322
+                    <el-input v-model="contractData.main.qtyShort" disabled></el-input>
323
+                  </el-form-item>
324
+                </el-col>
325
+              </el-row>
326
+              <el-row :gutter="20">
327
+                <el-col :span="8">
328
+                  <el-form-item label="金额溢装%">
329
+                    <el-input v-model="contractData.main.amtOver" disabled></el-input>
330
+                  </el-form-item>
331
+                </el-col>
332
+                <el-col :span="8">
333
+                  <el-form-item label="金额短装%">
334
+                    <el-input v-model="contractData.main.amtShort" disabled></el-input>
335
+                  </el-form-item>
336
+                </el-col>
337
+                <el-col :span="8">
338
+                  <el-form-item label="保险种类">
339
+                    <el-input v-model="contractData.main.insuranceType" disabled></el-input>
340
+                  </el-form-item>
341
+                </el-col>
342
+              </el-row>
343
+              <el-row :gutter="20">
344
+                <el-col :span="8">
345
+                  <el-form-item label="保险费率%">
346
+                    <el-input v-model="contractData.main.insuranceAmt" disabled></el-input>
347
+                  </el-form-item>
348
+                </el-col>
349
+                <el-col :span="8">
350
+                  <el-form-item label="是否过磅数量结算">
351
+                    <el-input v-model="contractData.main.ifWeighedSalesQty" disabled></el-input>
352
+                  </el-form-item>
353
+                </el-col>
354
+                <el-col :span="8">
355
+                  <el-form-item label="结算指标">
356
+                    <el-input v-model="contractData.main.settIndex" disabled></el-input>
357
+                  </el-form-item>
358
+                </el-col>
359
+              </el-row>
360
+              <el-row :gutter="20">
361
+                <el-col :span="8">
362
+                  <el-form-item label="是否允许分批">
363
+                    <el-input v-model="contractData.main.ifBatch" disabled></el-input>
364
+                  </el-form-item>
365
+                </el-col>
366
+                <el-col :span="8">
367
+                  <el-form-item label="是否允许转运">
368
+                    <el-input v-model="contractData.main.ifTransfer" disabled></el-input>
369
+                  </el-form-item>
370
+                </el-col>
371
+                <el-col :span="8">
372
+                  <el-form-item label="FNO">
373
+                    <el-input v-model="contractData.main.fno" disabled></el-input>
374
+                  </el-form-item>
375
+                </el-col>
376
+              </el-row>
377
+              <el-row :gutter="20">
378
+                <el-col :span="12">
379
+                  <el-form-item label="合同描述">
380
+                    <el-input v-model="contractData.main.contrDesc" type="textarea" rows="2" disabled></el-input>
381
+                  </el-form-item>
382
+                </el-col>
383
+                <el-col :span="12">
384
+                  <el-form-item label="备注信息">
385
+                    <el-input v-model="contractData.main.remark" type="textarea" rows="2" disabled></el-input>
386
+                  </el-form-item>
387
+                </el-col>
388
+              </el-row>
389
+            </el-form>
390
+          </div>
391
+        </el-tab-pane>
392
+        
393
+        <!-- 合同产品明细(第二个sheet) -->
394
+        <el-tab-pane label="合同产品明细" name="products">
395
+          <div class="table-section">
396
+            <el-table :data="contractData.products" style="width: 100%" height="500px">
397
+              <el-table-column prop="fno" label="销售合同号" width="150"></el-table-column>
398
+              <el-table-column prop="itemno" label="商品编号" width="120"></el-table-column>
399
+              <el-table-column prop="curcyPo" label="采购币种" width="100"></el-table-column>
400
+              <el-table-column prop="inTaxRate" label="增值税率%" width="100"></el-table-column>
401
+              <el-table-column prop="ironCont" label="含铁量%" width="100"></el-table-column>
402
+              <el-table-column prop="moisture" label="水分%" width="80"></el-table-column>
403
+              <el-table-column prop="coalMoisture" label="煤焦计价水%" width="120"></el-table-column>
404
+              <el-table-column prop="poprice" label="采购单价" width="100"></el-table-column>
405
+              <el-table-column prop="priceMtd" label="计价方式" width="100"></el-table-column>
406
+              <el-table-column prop="qty" label="数量" width="100"></el-table-column>
407
+              <el-table-column prop="ratePo" label="采购汇率" width="100"></el-table-column>
408
+              <el-table-column prop="reTaxRate" label="退税率%" width="100"></el-table-column>
409
+              <el-table-column prop="resourceNo" label="资源号" width="120"></el-table-column>
410
+              <el-table-column prop="SMRNo" label="钢厂资源号" width="120"></el-table-column>
411
+              <el-table-column prop="batchNo" label="批次号" width="120"></el-table-column>
412
+              <el-table-column prop="sdesc" label="中文品名" width="150"></el-table-column>
413
+              <el-table-column prop="edesc" label="英文品名" width="150"></el-table-column>
414
+              <el-table-column prop="spec" label="QP描述" width="150"></el-table-column>
415
+              <el-table-column prop="brand" label="牌号" width="100"></el-table-column>
416
+              <el-table-column prop="ftype3" label="规格描述" width="120"></el-table-column>
417
+              <el-table-column prop="qua" label="件数" width="80"></el-table-column>
418
+              <el-table-column prop="soPrice" label="销售单价" width="100"></el-table-column>
419
+              <el-table-column prop="fnum3" label="长" width="80"></el-table-column>
420
+              <el-table-column prop="fnum4" label="米重" width="80"></el-table-column>
421
+              <el-table-column prop="fnum8" label="捆数" width="80"></el-table-column>
422
+              <el-table-column prop="fnum6" label="捆单重" width="100"></el-table-column>
423
+              <el-table-column prop="fnum7" label="捆支数" width="100"></el-table-column>
424
+              <el-table-column prop="ftype5" label="色标" width="80"></el-table-column>
425
+              <el-table-column prop="ftype7" label="螺纹规格库" width="120"></el-table-column>
426
+              <el-table-column prop="ftype4" label="计重方式" width="100"></el-table-column>
427
+              <el-table-column prop="fnw" label="净重" width="100"></el-table-column>
428
+              <el-table-column prop="fgw" label="毛重" width="100"></el-table-column>
429
+              <el-table-column prop="taxAmt" label="税额" width="100"></el-table-column>
430
+              <el-table-column prop="ut" label="单位" width="80"></el-table-column>
431
+              <el-table-column prop="lPort" label="装运港" width="120"></el-table-column>
432
+              <el-table-column prop="dPort" label="目的港" width="120"></el-table-column>
433
+              <el-table-column prop="hsCode" label="海关编码" width="120"></el-table-column>
434
+              <el-table-column prop="hsNameCn" label="海关中文名称" width="150"></el-table-column>
435
+              <el-table-column prop="hsNameEn" label="海关英文名称" width="150"></el-table-column>
436
+              <el-table-column prop="remark" label="备注" width="150"></el-table-column>
437
+            </el-table>
438
+          </div>
439
+        </el-tab-pane>
440
+        
441
+        <!-- 收款计划(第三个sheet) -->
442
+        <el-tab-pane label="收款计划" name="payments">
443
+          <div class="table-section">
444
+            <el-table :data="contractData.payments" style="width: 100%">
445
+              <el-table-column prop="fno" label="销售合同号" width="150"></el-table-column>
446
+              <el-table-column prop="payDays" label="账期" width="100"></el-table-column>
447
+              <el-table-column prop="payMode" label="收款方式" width="120"></el-table-column>
448
+              <el-table-column prop="paymentRatio" label="款项比例%" width="120"></el-table-column>
449
+              <el-table-column prop="paymentType" label="款项类别" width="120"></el-table-column>
450
+              <el-table-column prop="ourBankNm" label="收款收证银行名称" width="180"></el-table-column>
451
+              <el-table-column prop="ourBankAcct" label="收款收证银行账户" width="200"></el-table-column>
452
+              <el-table-column prop="clauseRemark" label="备注" width="150"></el-table-column>
453
+            </el-table>
454
+          </div>
455
+        </el-tab-pane>
456
+        
457
+        <!-- 责任中心(第四个sheet) -->
458
+        <el-tab-pane label="责任中心" name="responsibility">
459
+          <div class="table-section">
460
+            <el-table :data="contractData.responsibility" style="width: 100%">
461
+              <el-table-column prop="fno" label="销售合同号" width="150"></el-table-column>
462
+              <el-table-column prop="InstitutionId" label="公司代码" width="120"></el-table-column>
463
+              <el-table-column prop="InstitutionNm" label="公司名称" width="150"></el-table-column>
464
+              <el-table-column prop="assessQtyRate" label="利润考核占比%" width="120"></el-table-column>
465
+              <el-table-column prop="assessRatio" label="金额考核占比%" width="120"></el-table-column>
466
+              <el-table-column prop="dept_id" label="制单人部门代码" width="120"></el-table-column>
467
+              <el-table-column prop="dept_name" label="制单人部门名称" width="150"></el-table-column>
468
+              <el-table-column prop="personDeptId" label="部门代码" width="120"></el-table-column>
469
+              <el-table-column prop="personDeptNm" label="部门名称" width="150"></el-table-column>
470
+            </el-table>
471
+          </div>
472
+        </el-tab-pane>
473
+        
474
+        <!-- 费用明细(第五个sheet) -->
475
+        <el-tab-pane label="费用明细" name="expenses">
476
+          <div class="table-section">
477
+            <el-table :data="contractData.expenses" style="width: 100%">
478
+              <el-table-column prop="fno" label="销售合同号" width="150"></el-table-column>
479
+              <el-table-column prop="feeCd" label="费用代码" width="120"></el-table-column>
480
+              <el-table-column prop="feeNm" label="费用名称" width="150"></el-table-column>
481
+              <el-table-column prop="taxRate" label="费用税率%" width="120"></el-table-column>
482
+              <el-table-column prop="feeDesc" label="费用描述" width="180"></el-table-column>
483
+              <el-table-column prop="curcy" label="币别" width="100"></el-table-column>
484
+              <el-table-column prop="rate" label="汇率" width="100"></el-table-column>
485
+              <el-table-column prop="feeAmt" label="费用金额" width="120"></el-table-column>
486
+              <el-table-column prop="allocationCategory" label="费用分摊类型" width="150"></el-table-column>
487
+              <el-table-column prop="remark" label="备注" width="150"></el-table-column>
488
+            </el-table>
489
+          </div>
490
+        </el-tab-pane>
491
+        
492
+        <!-- 服务费率(第六个sheet) -->
493
+        <el-tab-pane label="服务费率" name="serviceRates">
494
+          <div class="table-section">
495
+            <el-table :data="contractData.serviceRates" style="width: 100%">
496
+              <el-table-column prop="fno" label="销售合同号" width="150"></el-table-column>
497
+              <el-table-column prop="paymentType" label="款项类型" width="120"></el-table-column>
498
+              <el-table-column prop="startDays" label="开始天数" width="120"></el-table-column>
499
+              <el-table-column prop="endDays" label="结束天数" width="120"></el-table-column>
500
+              <el-table-column prop="annualRate" label="年化费率%" width="120"></el-table-column>
501
+              <el-table-column prop="minDays" label="起步天数" width="120"></el-table-column>
502
+            </el-table>
503
+          </div>
504
+        </el-tab-pane>
505
+      </el-tabs>
506
+      
507
+      <!-- 保存按钮 -->
508
+      <div class="action-section">
509
+        <el-button type="primary" @click="handleSave">保存合同</el-button>
510
+      </div>
511
+    </div>
512
+  </div>
513
+</template>
514
+
515
+<script setup>
516
+import { ref, onMounted } from 'vue'
517
+import { ElMessage } from 'element-plus'
518
+import { UploadFilled } from '@element-plus/icons-vue'
519
+import { importContractExcel } from '../api/contractApi'
520
+import * as XLSX from 'xlsx'
521
+
522
+// 响应式数据
523
+const selectedFile = ref(null)
524
+const contractData = ref(null)
525
+const activeTab = ref('main')
526
+// 处理文件选择
527
+const handleFileChange = (file) => {
528
+  selectedFile.value = file.raw
529
+}
530
+    
531
+// 解析Excel文件并预览内容
532
+const previewFileContent = async (file) => {
533
+  try {
534
+    const data = await parseExcelFile(file);
535
+    contractData.value = data;
536
+  } catch (error) {
537
+    ElMessage.error('文件解析失败:' + (error.message || '未知错误'));
538
+    // 解析失败时使用空数据
539
+    contractData.value = {
540
+      main: {},
541
+      products: [],
542
+      payments: [],
543
+      responsibility: [],
544
+      expenses: [],
545
+      serviceRates: []
546
+    };
547
+  }
548
+};
549
+    
550
+// 解析Excel文件的核心方法
551
+const parseExcelFile = (file) => {
552
+  return new Promise((resolve, reject) => {
553
+    const reader = new FileReader();
554
+    
555
+    reader.onload = (e) => {
556
+      try {
557
+        // 读取Excel文件
558
+        const data = new Uint8Array(e.target.result);
559
+        const workbook = XLSX.read(data, { type: 'array' });
560
+        
561
+        // 初始化合同数据对象
562
+        const resultData = {
563
+          main: {},
564
+          products: [],
565
+          payments: [],
566
+          responsibility: [],
567
+          expenses: [],
568
+          serviceRates: []
569
+        };
570
+        
571
+        // 获取所有sheet
572
+        const sheetNames = workbook.SheetNames;
573
+        
574
+        // 解析第一个sheet(主表信息)
575
+        if (sheetNames.length >= 1) {
576
+          const mainSheet = workbook.Sheets[sheetNames[0]];
577
+          const mainData = XLSX.utils.sheet_to_json(mainSheet, { header: 1 });
578
+          
579
+          // 处理主表数据(根据Excel文件结构调整,可能第一行是标题,第二行是字段名)
580
+          if (mainData.length >= 2) {
581
+            // 尝试从不同行查找有效的字段名和数据
582
+            // 假设第二行(mainData[1])是字段名行
583
+            const headers = mainData[1]; // 第二行作为字段名
584
+            // 数据可能在第三行或直接在字段名下一行
585
+            let valuesRowIndex = 2;
586
+            
587
+            // 检查第三行是否存在且包含有效数据
588
+            if (mainData.length <= valuesRowIndex || !mainData[valuesRowIndex] || 
589
+                mainData[valuesRowIndex].every(cell => cell === undefined || cell === null || cell === '')) {
590
+              // 如果第三行无效,尝试使用第二行作为数据(可能Excel结构不同)
591
+              valuesRowIndex = 1;
592
+            }
593
+            
594
+            const values = mainData[valuesRowIndex];
595
+            
596
+            console.log('检测到的Excel结构:');
597
+            console.log('- 假设字段名行索引:', 1);
598
+            console.log('- 假设数据行索引:', valuesRowIndex);
599
+            console.log('主表字段名:', headers);
600
+            console.log('主表数据行:', values);
601
+            
602
+            // 构建主表数据对象
603
+            headers.forEach((header, index) => {
604
+              if (header && values[index] !== undefined && values[index] !== null && values[index] !== '') {
605
+                // 获取映射后的字段名
606
+                const normalizedName = normalizeFieldName(header);
607
+                
608
+                // 存储实际数据值到main对象中
609
+                resultData.main[normalizedName] = values[index];
610
+                resultData.main[header] = values[index];
611
+                
612
+                // 增加更详细的调试信息
613
+                console.log(`数据映射: 字段名=${header}, 映射名=${normalizedName}, 数据值=${values[index]}`);
614
+              }
615
+            });
616
+          }
617
+        }
618
+        
619
+        // 解析第二个sheet(合同产品明细)
620
+        if (sheetNames.length >= 2) {
621
+          const productsSheet = workbook.Sheets[sheetNames[1]];
622
+          const productsData = XLSX.utils.sheet_to_json(productsSheet, { header: 1 });
623
+          
624
+          console.log('产品明细sheet行数:', productsData.length);
625
+          
626
+          // 处理产品明细数据(第三行开始是实际数据)
627
+          if (productsData.length >= 3) {
628
+            const headers = productsData[1]; // 第二行作为字段名
629
+            console.log('产品明细字段名:', headers);
630
+            
631
+            // 从第三行开始处理数据行,确保不包含标题行
632
+            for (let i = 2; i < productsData.length; i++) {
633
+              const row = productsData[i];
634
+              
635
+              // 跳过空行
636
+              if (!row || row.every(cell => cell === undefined || cell === null || cell === '')) {
637
+                continue;
638
+              }
639
+              
640
+              // 更严格地跳过标题行(检查第一列和其他关键列是否包含标题文本)
641
+              const firstCell = row[0] ? String(row[0]) : '';
642
+              const mightBeHeaderRow = 
643
+                firstCell.includes('商品编号') || 
644
+                firstCell.includes('产品编号') ||
645
+                firstCell.includes('序号') ||
646
+                (headers[0] && row[0] === headers[0]); // 如果第一列值与表头相同,可能是重复的表头
647
+              
648
+              if (mightBeHeaderRow) {
649
+                console.log('跳过标题行:', i, row);
650
+                continue;
651
+              }
652
+              
653
+              console.log('产品数据行:', i, row);
654
+              const productItem = {};
655
+              headers.forEach((header, index) => {
656
+                if (header && row[index] !== undefined) {
657
+                  // 确保我们存储的是单元格的值,而不是表头名
658
+                  const fieldName = normalizeFieldName(header);
659
+                  // 同时保存原始字段名和映射后的字段名,确保与模板字段名匹配
660
+                  productItem[fieldName] = row[index];
661
+                  productItem[header] = row[index];
662
+                  console.log(`产品映射: ${header} -> ${fieldName}, 值: ${row[index]}`);
663
+                }
664
+              });
665
+              
666
+              // 只有当产品项包含有意义的数据时才添加
667
+              if (Object.keys(productItem).length > 0) {
668
+                resultData.products.push(productItem);
669
+              }
670
+            }
671
+          }
672
+        }
673
+        
674
+        // 解析第三个sheet(收款计划)
675
+        if (sheetNames.length >= 3) {
676
+          const paymentsSheet = workbook.Sheets[sheetNames[2]];
677
+          const paymentsData = XLSX.utils.sheet_to_json(paymentsSheet, { header: 1 });
678
+          
679
+          if (paymentsData.length >= 3) {
680
+            const headers = paymentsData[1];
681
+            
682
+            for (let i = 2; i < paymentsData.length; i++) {
683
+              const row = paymentsData[i];
684
+              if (!row || row.every(cell => cell === undefined || cell === null || cell === '')) {
685
+                continue;
686
+              }
687
+              
688
+              const paymentItem = {};
689
+              headers.forEach((header, index) => {
690
+                if (header && row[index] !== undefined) {
691
+                  const fieldName = normalizeFieldName(header);
692
+                  paymentItem[fieldName] = row[index];
693
+                  paymentItem[header] = row[index];
694
+                }
695
+              });
696
+              
697
+              resultData.payments.push(paymentItem);
698
+            }
699
+          }
700
+        }
701
+        
702
+        // 解析第四个sheet(责任中心)
703
+        if (sheetNames.length >= 4) {
704
+          const responsibilitySheet = workbook.Sheets[sheetNames[3]];
705
+          const responsibilityData = XLSX.utils.sheet_to_json(responsibilitySheet, { header: 1 });
706
+          
707
+          if (responsibilityData.length >= 3) {
708
+            const headers = responsibilityData[1];
709
+            
710
+            for (let i = 2; i < responsibilityData.length; i++) {
711
+              const row = responsibilityData[i];
712
+              if (!row || row.every(cell => cell === undefined || cell === null || cell === '')) {
713
+                continue;
714
+              }
715
+              
716
+              const respItem = {};
717
+              headers.forEach((header, index) => {
718
+                if (header && row[index] !== undefined) {
719
+                  const fieldName = normalizeFieldName(header);
720
+                  respItem[fieldName] = row[index];
721
+                  respItem[header] = row[index];
722
+                }
723
+              });
724
+              
725
+              resultData.responsibility.push(respItem);
726
+            }
727
+          }
728
+        }
729
+        
730
+        // 解析第五个sheet(费用明细)
731
+        if (sheetNames.length >= 5) {
732
+          const expensesSheet = workbook.Sheets[sheetNames[4]];
733
+          const expensesData = XLSX.utils.sheet_to_json(expensesSheet, { header: 1 });
734
+          
735
+          if (expensesData.length >= 3) {
736
+            const headers = expensesData[1];
737
+            
738
+            for (let i = 2; i < expensesData.length; i++) {
739
+              const row = expensesData[i];
740
+              if (!row || row.every(cell => cell === undefined || cell === null || cell === '')) {
741
+                continue;
742
+              }
743
+              
744
+              const expenseItem = {};
745
+              headers.forEach((header, index) => {
746
+                if (header && row[index] !== undefined) {
747
+                  const fieldName = normalizeFieldName(header);
748
+                  expenseItem[fieldName] = row[index];
749
+                  expenseItem[header] = row[index];
750
+                }
751
+              });
752
+              
753
+              resultData.expenses.push(expenseItem);
754
+            }
755
+          }
756
+        }
757
+        
758
+        // 解析第六个sheet(服务费率)
759
+        if (sheetNames.length >= 6) {
760
+          const serviceRatesSheet = workbook.Sheets[sheetNames[5]];
761
+          const serviceRatesData = XLSX.utils.sheet_to_json(serviceRatesSheet, { header: 1 });
762
+          
763
+          if (serviceRatesData.length >= 3) {
764
+            const headers = serviceRatesData[1];
765
+            
766
+            for (let i = 2; i < serviceRatesData.length; i++) {
767
+              const row = serviceRatesData[i];
768
+              if (!row || row.every(cell => cell === undefined || cell === null || cell === '')) {
769
+                continue;
770
+              }
771
+              
772
+              const rateItem = {};
773
+              headers.forEach((header, index) => {
774
+                if (header && row[index] !== undefined) {
775
+                  const fieldName = normalizeFieldName(header);
776
+                  rateItem[fieldName] = row[index];
777
+                  rateItem[header] = row[index];
778
+                }
779
+              });
780
+              
781
+              resultData.serviceRates.push(rateItem);
782
+            }
783
+          }
784
+        }
785
+        
786
+        resolve(resultData);
787
+      } catch (error) {
788
+        reject(error);
789
+      }
790
+    };
791
+    
792
+    reader.onerror = () => {
793
+      reject(new Error('文件读取失败'));
794
+    };
795
+    
796
+    reader.readAsArrayBuffer(file);
797
+  });
798
+};
799
+    
800
+// 标准化字段名,将Excel表头转换为Vue组件使用的属性名
801
+const normalizeFieldName = (header) => {
802
+  // 添加类型检查,确保header是字符串
803
+  if (typeof header !== 'string') {
804
+    return ''; // 对于非字符串类型,返回空字符串
805
+  }
806
+  
807
+  // 字段名映射,处理可能的表头变化,确保与页面模板绑定的字段名一致
808
+  const fieldMappings = {
809
+    // 主表字段映射
810
+    '销售合同号': 'contractCode',
811
+    '合同编号': 'contractCode',
812
+    '合同类型': 'contractType',
813
+    '合同产品类型': 'contractProductType',
814
+    '业务类型': 'serviceType',
815
+    '币别': 'curcy',
816
+    '签约日期': 'signDate',
817
+    '履约截止日期': 'performEndDate',
818
+    '客户名称': 'customerName',
819
+    '客户代码': 'customerCode',
820
+    '我方开户银行': 'ourBankNm',
821
+    '我方银行账号': 'ourBankAcct',
822
+    '是否长协': 'ifLongTerm',
823
+    '装运港': 'lPort',
824
+    '目的港': 'dPort',
825
+    '我方编号': 'fours',
826
+    '我方名称': 'foursname',
827
+    '采购合同号': 'poNo',
828
+    '合同状态': 'contractStatus',
829
+    '签约地点': 'signLoc',
830
+    '折人民币汇率': 'rate',
831
+    '执行美元汇率': 'rateUs',
832
+    '价格条款': 'terms',
833
+    '装卸条款': 'loadUnloadTerms',
834
+    '客户联系人名称': 'ctContNm',
835
+    '客户开户银行': 'ctBankName',
836
+    '客户银行账户': 'ctBankAcct',
837
+    '客户合同号': 'ctOrderNo',
838
+    '制单人代码': 'createBy',
839
+    '制单人名称': 'createByName',
840
+    '合同执行人代码': 'contrExecCd',
841
+    '合同执行人名称': 'contrExecNm',
842
+    
843
+    // 产品明细字段映射
844
+    '商品编号': 'itemno',
845
+    '采购币种': 'curcyPo',
846
+    '增值税率': 'inTaxRate',
847
+    '含铁量': 'ironCont',
848
+    '水分': 'moisture',
849
+    '煤焦计价水': 'coalMoisture',
850
+    '采购单价': 'poprice',
851
+    '计价方式': 'priceMtd',
852
+    '数量': 'qty',
853
+    '采购汇率': 'ratePo',
854
+    '退税率': 'reTaxRate',
855
+    '资源号': 'resourceNo',
856
+    '钢厂资源号': 'SMRNo',
857
+    '批次号': 'batchNo',
858
+    '中文品名': 'sdesc',
859
+    '英文品名': 'edesc',
860
+    'qp描述': 'spec',
861
+    '牌号': 'brand',
862
+    '规格描述': 'ftype3',
863
+    '件数': 'qua',
864
+    '销售单价': 'soPrice',
865
+    '长': 'fnum3',
866
+    '米重': 'fnum4',
867
+    '捆数': 'fnum8',
868
+    '捆单重': 'fnum6',
869
+    '捆支数': 'fnum7',
870
+    '色标': 'ftype5',
871
+    '螺纹规格库': 'ftype7',
872
+    '计重方式': 'ftype4',
873
+    '净重': 'fnw',
874
+    '毛重': 'fgw',
875
+    '税额': 'taxAmt',
876
+    '单位': 'ut',
877
+    '装运港': 'lPort',
878
+    '目的港': 'dPort',
879
+    '海关编码': 'hsCode',
880
+    '海关中文名称': 'hsNameCn',
881
+    '海关英文名称': 'hsNameEn',
882
+    '备注': 'remark',
883
+    
884
+    // 其他模块字段映射
885
+    '账期': 'payDays',
886
+    '收款方式': 'payMode',
887
+    '款项比例': 'paymentRatio',
888
+    '款项类别': 'paymentType',
889
+    '收款收证银行名称': 'ourBankNm',
890
+    '收款收证银行账户': 'ourBankAcct',
891
+    '公司代码': 'InstitutionId',
892
+    '公司名称': 'InstitutionNm',
893
+    '利润考核占比': 'assessQtyRate',
894
+    '金额考核占比': 'assessRatio',
895
+    '制单人部门代码': 'dept_id',
896
+    '制单人部门名称': 'dept_name',
897
+    '部门代码': 'personDeptId',
898
+    '部门名称': 'personDeptNm',
899
+    '费用代码': 'feeCd',
900
+    '费用名称': 'feeNm',
901
+    '费用税率': 'taxRate',
902
+    '费用描述': 'feeDesc',
903
+    '汇率': 'rate',
904
+    '费用金额': 'feeAmt',
905
+    '费用分摊类型': 'allocationCategory',
906
+    '开始天数': 'startDays',
907
+    '结束天数': 'endDays',
908
+    '年化费率': 'annualRate',
909
+    '起步天数': 'minDays'
910
+  };
911
+  
912
+  // 首先检查是否有直接映射(使用原始header)
913
+  if (fieldMappings[header]) {
914
+    return fieldMappings[header];
915
+  }
916
+  
917
+  // 移除空格和特殊字符,转换为小写
918
+  const normalized = header.trim().toLowerCase()
919
+    .replace(/[\s-_]+/g, '')
920
+    .replace(/[*::]/g, '');
921
+  
922
+  // 再次检查标准化后的字段名是否有映射
923
+  if (fieldMappings[normalized]) {
924
+    return fieldMappings[normalized];
925
+  }
926
+  
927
+  return normalized;
928
+};
929
+    
930
+// 处理文件导入
931
+const handleImport = async () => {
932
+  if (!selectedFile.value) {
933
+    ElMessage.warning('请先选择文件');
934
+    return;
935
+  }
936
+  
937
+  try {
938
+    ElMessage.info('正在导入文件,请稍候...');
939
+    
940
+    // 使用前端直接解析Excel文件
941
+    await previewFileContent(selectedFile.value);
942
+    
943
+    ElMessage.success('文件导入成功');
944
+  } catch (error) {
945
+    ElMessage.error('导入失败:' + (error.message || '未知错误'));
946
+  }
947
+};
948
+    
949
+// 保存合同
950
+const handleSave = () => {
951
+  try {
952
+    // 这里应该调用保存接口
953
+    ElMessage.success('合同保存成功')
954
+  } catch (error) {
955
+    ElMessage.error('保存失败:' + (error.message || '未知错误'))
956
+  }
957
+}
958
+</script setup>
959
+
960
+<style scoped>
961
+.contract-container {
962
+  padding: 20px;
963
+}
964
+
965
+.import-section {
966
+  margin-bottom: 30px;
967
+  padding: 20px;
968
+  background-color: #f5f7fa;
969
+  border-radius: 8px;
970
+}
971
+
972
+.el-upload {
973
+  margin-bottom: 20px;
974
+}
975
+
976
+.contract-info {
977
+  margin-top: 30px;
978
+}
979
+
980
+.form-section {
981
+  padding: 20px;
982
+  background-color: #fff;
983
+}
984
+
985
+.table-section {
986
+  padding: 20px;
987
+  background-color: #fff;
988
+}
989
+
990
+.action-section {
991
+  margin-top: 20px;
992
+  text-align: center;
993
+}
994
+</style>

+ 94
- 0
src/views/UserAdd.vue Datei anzeigen

@@ -0,0 +1,94 @@
1
+<template>
2
+  <div class="user-add">
3
+    <h1>添加用户</h1>
4
+    <form @submit.prevent="submitForm">
5
+      <div class="form-group">
6
+        <label for="username">用户名</label>
7
+        <input type="text" id="username" v-model="userForm.username" required>
8
+      </div>
9
+      <div class="form-group">
10
+        <label for="password">密码</label>
11
+        <input type="password" id="password" v-model="userForm.password" required>
12
+      </div>
13
+      <div class="form-group">
14
+        <label for="nickname">昵称</label>
15
+        <input type="text" id="nickname" v-model="userForm.nickname" required>
16
+      </div>
17
+      <div class="form-group">
18
+        <label for="email">邮箱</label>
19
+        <input type="email" id="email" v-model="userForm.email">
20
+      </div>
21
+      <div class="form-group">
22
+        <label for="phone">电话</label>
23
+        <input type="text" id="phone" v-model="userForm.phone">
24
+      </div>
25
+      <div class="form-group">
26
+        <label for="status">状态</label>
27
+        <select id="status" v-model="userForm.status">
28
+          <option value="1">启用</option>
29
+          <option value="0">禁用</option>
30
+        </select>
31
+      </div>
32
+      <div class="form-actions">
33
+        <button type="submit">保存</button>
34
+        <button type="button" @click="router.back()">取消</button>
35
+      </div>
36
+    </form>
37
+  </div>
38
+</template>
39
+
40
+<script setup>
41
+import { reactive } from 'vue'
42
+import { useRouter } from 'vue-router'
43
+import { addUser } from '../api/userApi'
44
+
45
+const router = useRouter()
46
+const userForm = reactive({
47
+  username: '',
48
+  password: '',
49
+  nickname: '',
50
+  email: '',
51
+  phone: '',
52
+  status: 1
53
+})
54
+
55
+const submitForm = async () => {
56
+  try {
57
+    await addUser(userForm)
58
+    router.push('/users')
59
+  } catch (error) {
60
+    console.error('添加用户失败:', error)
61
+  }
62
+}
63
+</script>
64
+
65
+<style scoped>
66
+.user-add {
67
+  padding: 2rem;
68
+  max-width: 600px;
69
+  margin: 0 auto;
70
+}
71
+
72
+.form-group {
73
+  margin-bottom: 1rem;
74
+  text-align: left;
75
+}
76
+
77
+label {
78
+  display: block;
79
+  margin-bottom: 0.5rem;
80
+}
81
+
82
+input, select {
83
+  width: 100%;
84
+  padding: 0.5rem;
85
+  box-sizing: border-box;
86
+}
87
+
88
+.form-actions {
89
+  margin-top: 2rem;
90
+  display: flex;
91
+  gap: 1rem;
92
+  justify-content: center;
93
+}
94
+</style>

+ 116
- 0
src/views/UserEdit.vue Datei anzeigen

@@ -0,0 +1,116 @@
1
+<template>
2
+  <div class="user-edit">
3
+    <h1>编辑用户</h1>
4
+    <form @submit.prevent="submitForm">
5
+      <div class="form-group">
6
+        <label for="username">用户名</label>
7
+        <input type="text" id="username" v-model="userForm.username" required>
8
+      </div>
9
+      <div class="form-group">
10
+        <label for="password">密码(不修改请留空)</label>
11
+        <input type="password" id="password" v-model="userForm.password">
12
+      </div>
13
+      <div class="form-group">
14
+        <label for="nickname">昵称</label>
15
+        <input type="text" id="nickname" v-model="userForm.nickname" required>
16
+      </div>
17
+      <div class="form-group">
18
+        <label for="email">邮箱</label>
19
+        <input type="email" id="email" v-model="userForm.email">
20
+      </div>
21
+      <div class="form-group">
22
+        <label for="phone">电话</label>
23
+        <input type="text" id="phone" v-model="userForm.phone">
24
+      </div>
25
+      <div class="form-group">
26
+        <label for="status">状态</label>
27
+        <select id="status" v-model="userForm.status">
28
+          <option value="1">启用</option>
29
+          <option value="0">禁用</option>
30
+        </select>
31
+      </div>
32
+      <div class="form-actions">
33
+        <button type="submit">保存</button>
34
+        <button type="button" @click="router.back()">取消</button>
35
+      </div>
36
+    </form>
37
+  </div>
38
+</template>
39
+
40
+<script setup>
41
+import { reactive, onMounted } from 'vue'
42
+import { useRouter, useRoute } from 'vue-router'
43
+import { getUserById, updateUser } from '../api/userApi'
44
+
45
+const router = useRouter()
46
+const route = useRoute()
47
+const userForm = reactive({
48
+  id: null,
49
+  username: '',
50
+  password: '',
51
+  nickname: '',
52
+  email: '',
53
+  phone: '',
54
+  status: 1
55
+})
56
+
57
+const loadUser = async () => {
58
+  const id = route.params.id
59
+  try {
60
+    const response = await getUserById(id)
61
+    const user = response.data
62
+    Object.assign(userForm, user)
63
+  } catch (error) {
64
+    console.error('获取用户信息失败:', error)
65
+  }
66
+}
67
+
68
+const submitForm = async () => {
69
+  try {
70
+    // 如果密码为空,不更新密码
71
+    const updateData = { ...userForm }
72
+    if (!updateData.password) {
73
+      delete updateData.password
74
+    }
75
+    await updateUser(updateData)
76
+    router.push('/users')
77
+  } catch (error) {
78
+    console.error('更新用户失败:', error)
79
+  }
80
+}
81
+
82
+onMounted(() => {
83
+  loadUser()
84
+})
85
+</script>
86
+
87
+<style scoped>
88
+.user-edit {
89
+  padding: 2rem;
90
+  max-width: 600px;
91
+  margin: 0 auto;
92
+}
93
+
94
+.form-group {
95
+  margin-bottom: 1rem;
96
+  text-align: left;
97
+}
98
+
99
+label {
100
+  display: block;
101
+  margin-bottom: 0.5rem;
102
+}
103
+
104
+input, select {
105
+  width: 100%;
106
+  padding: 0.5rem;
107
+  box-sizing: border-box;
108
+}
109
+
110
+.form-actions {
111
+  margin-top: 2rem;
112
+  display: flex;
113
+  gap: 1rem;
114
+  justify-content: center;
115
+}
116
+</style>

+ 111
- 0
src/views/UserList.vue Datei anzeigen

@@ -0,0 +1,111 @@
1
+<template>
2
+  <div class="user-list">
3
+    <h1>用户管理</h1>
4
+    <div class="actions">
5
+      <router-link to="/user/add">
6
+        <button class="add-btn">添加用户</button>
7
+      </router-link>
8
+    </div>
9
+    <table class="user-table">
10
+      <thead>
11
+        <tr>
12
+          <th>ID</th>
13
+          <th>用户名</th>
14
+          <th>昵称</th>
15
+          <th>邮箱</th>
16
+          <th>电话</th>
17
+          <th>状态</th>
18
+          <th>操作</th>
19
+        </tr>
20
+      </thead>
21
+      <tbody>
22
+        <tr v-for="user in users" :key="user.id">
23
+          <td>{{ user.id }}</td>
24
+          <td>{{ user.username }}</td>
25
+          <td>{{ user.nickname }}</td>
26
+          <td>{{ user.email }}</td>
27
+          <td>{{ user.phone }}</td>
28
+          <td>{{ user.status === 1 ? '启用' : '禁用' }}</td>
29
+          <td>
30
+            <button @click="editUser(user.id)">编辑</button>
31
+            <button @click="deleteUser(user.id)" class="delete-btn">删除</button>
32
+          </td>
33
+        </tr>
34
+      </tbody>
35
+    </table>
36
+  </div>
37
+</template>
38
+
39
+<script setup>
40
+import { ref, onMounted } from 'vue'
41
+import { useRouter } from 'vue-router'
42
+import { getUsers, deleteUserById } from '../api/userApi'
43
+
44
+const router = useRouter()
45
+const users = ref([])
46
+
47
+const loadUsers = async () => {
48
+  try {
49
+    const response = await getUsers()
50
+    users.value = response.data
51
+  } catch (error) {
52
+    console.error('获取用户列表失败:', error)
53
+  }
54
+}
55
+
56
+const editUser = (id) => {
57
+  router.push(`/user/edit/${id}`)
58
+}
59
+
60
+const deleteUser = async (id) => {
61
+  if (confirm('确定要删除该用户吗?')) {
62
+    try {
63
+      await deleteUserById(id)
64
+      loadUsers()
65
+    } catch (error) {
66
+      console.error('删除用户失败:', error)
67
+    }
68
+  }
69
+}
70
+
71
+onMounted(() => {
72
+  loadUsers()
73
+})
74
+</script>
75
+
76
+<style scoped>
77
+.user-list {
78
+  padding: 2rem;
79
+}
80
+
81
+.actions {
82
+  margin-bottom: 1rem;
83
+  text-align: right;
84
+}
85
+
86
+.add-btn {
87
+  background-color: #646cff;
88
+  color: white;
89
+}
90
+
91
+.user-table {
92
+  width: 100%;
93
+  border-collapse: collapse;
94
+}
95
+
96
+.user-table th,
97
+.user-table td {
98
+  padding: 0.75rem;
99
+  text-align: left;
100
+  border-bottom: 1px solid #ddd;
101
+}
102
+
103
+.delete-btn {
104
+  background-color: #ff4444;
105
+  color: white;
106
+}
107
+
108
+button {
109
+  margin-right: 0.5rem;
110
+}
111
+</style>

+ 18
- 0
vite.config.js Datei anzeigen

@@ -0,0 +1,18 @@
1
+import { defineConfig } from 'vite'
2
+import vue from '@vitejs/plugin-vue'
3
+
4
+// https://vitejs.dev/config/
5
+export default defineConfig({
6
+  plugins: [vue()],
7
+  server: {
8
+    port: 3000,
9
+    open: true,
10
+    proxy: {
11
+      '/api': {
12
+        target: 'http://localhost:8080',
13
+        changeOrigin: true,
14
+        rewrite: (path) => path.replace(/^\/api/, '')
15
+      }
16
+    }
17
+  }
18
+})

Laden…
Abbrechen
Speichern