Skip to content

Commit 959c8d8

Browse files
committed
feat(admin): 添加工资发放功能
- 在管理员界面添加工资发放模块,支持员工工资列表管理 - 实现添加和删除员工工资条目的功能 - 集成本地存储功能保存工资列表数据 - 添加确认对话框防止误操作 - 在日志记录中增加操作人员信息 - 适配移动端界面的工资发放功能 - 实现批量发放工资的API调用功能
1 parent a8e5245 commit 959c8d8

File tree

3 files changed

+235
-1
lines changed

3 files changed

+235
-1
lines changed

src/main/java/org/b3log/symphony/processor/AdminProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3013,7 +3013,7 @@ public void paySalary(final RequestContext context) {
30133013
logMessage.append("\n完成时间:").append(completeTime);
30143014

30153015
// 生成日志
3016-
LogsService.simpleLog(context, "发放工资", logMessage.toString());
3016+
LogsService.simpleLog(context, "发放工资", "操作人员:" + currentUser.optString(User.USER_NAME) + " " + logMessage.toString());
30173017

30183018
// 返回结果
30193019
final JSONObject result = new JSONObject();

src/main/resources/skins/classic/admin/misc.ftl

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,128 @@
9797
</div>
9898
</div>
9999
</#if>
100+
101+
<div class="module">
102+
<div class="module-header">
103+
<h2>发放工资</h2>
104+
</div>
105+
<div class="module-panel form fn-clear form--admin">
106+
<div class="fn__flex">
107+
<label>
108+
<div>员工工资列表</div>
109+
<table id="salaryTable" class="table" style="width: 100%;">
110+
<thead>
111+
<tr>
112+
<th>用户名</th>
113+
<th>工资数额</th>
114+
<th>操作</th>
115+
</tr>
116+
</thead>
117+
<tbody id="salaryTableBody">
118+
</tbody>
119+
</table>
120+
<button type="button" onclick="SalaryUtil.addEmployeeRow()" class="green">+ 添加员工</button>
121+
</label>
122+
</div>
123+
<br/>
124+
<button type="button" onclick="SalaryUtil.paySalary()" class="green fn-right">发放工资</button>
125+
</div>
126+
</div>
100127
</div>
128+
129+
<script>
130+
const SalaryUtil = {
131+
STORAGE_KEY: 'admin_salary_list',
132+
servePath: '${servePath}',
133+
134+
init: function() {
135+
this.loadFromStorage();
136+
},
137+
138+
addEmployeeRow: function(data) {
139+
const tbody = document.getElementById('salaryTableBody');
140+
const row = document.createElement('tr');
141+
const userName = data ? data.userName : '';
142+
const amount = data ? data.amount : '';
143+
row.innerHTML =
144+
'<td><input type="text" class="salary-username" value="' + userName + '" placeholder="请输入用户名" oninput="SalaryUtil.saveToStorage()"></td>' +
145+
'<td><input type="number" class="salary-amount" value="' + amount + '" placeholder="请输入工资数额" min="1" oninput="SalaryUtil.saveToStorage()"></td>' +
146+
'<td><button type="button" onclick="SalaryUtil.removeEmployeeRow(this)">删除</button></td>';
147+
tbody.appendChild(row);
148+
this.saveToStorage();
149+
},
150+
151+
removeEmployeeRow: function(btn) {
152+
const row = btn.closest('tr');
153+
row.remove();
154+
this.saveToStorage();
155+
},
156+
157+
saveToStorage: function() {
158+
const data = this.getEmployeeData();
159+
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
160+
},
161+
162+
loadFromStorage: function() {
163+
const stored = localStorage.getItem(this.STORAGE_KEY);
164+
if (stored) {
165+
try {
166+
const data = JSON.parse(stored);
167+
if (Array.isArray(data) && data.length > 0) {
168+
document.getElementById('salaryTableBody').innerHTML = '';
169+
data.forEach(emp => this.addEmployeeRow(emp));
170+
return;
171+
}
172+
} catch (e) {
173+
console.error('Failed to load salary list:', e);
174+
}
175+
}
176+
// 默认添加一行
177+
this.addEmployeeRow();
178+
},
179+
180+
getEmployeeData: function() {
181+
const rows = document.querySelectorAll('#salaryTableBody tr');
182+
const data = [];
183+
rows.forEach(row => {
184+
const userName = row.querySelector('.salary-username').value.trim();
185+
const amount = parseInt(row.querySelector('.salary-amount').value);
186+
if (userName && amount > 0) {
187+
data.push({ userName: userName, amount: amount });
188+
}
189+
});
190+
return data;
191+
},
192+
193+
paySalary: function() {
194+
const data = this.getEmployeeData();
195+
if (data.length === 0) {
196+
alert('请至少添加一名员工');
197+
return;
198+
}
199+
200+
if (!confirm('确认向 ' + data.length + ' 名员工发放工资?')) {
201+
return;
202+
}
203+
204+
fetch(this.servePath + '/admin/pay-salary', {
205+
method: 'POST',
206+
headers: {
207+
'Content-Type': 'application/json'
208+
},
209+
body: JSON.stringify({ employees: data })
210+
})
211+
.then(res => res.json())
212+
.then(result => {
213+
alert(result.msg + '\n\n' + result.logMessage);
214+
})
215+
.catch(err => {
216+
alert('请求失败:' + err.message);
217+
});
218+
}
219+
};
220+
221+
// 页面加载时初始化
222+
SalaryUtil.init();
223+
</script>
101224
</@admin>

src/main/resources/skins/mobile/admin/misc.ftl

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,116 @@
8484
</div>
8585
</div>
8686
</#if>
87+
88+
<div class="module">
89+
<div class="module-header">
90+
<h2>发放工资</h2>
91+
</div>
92+
<div class="module-panel form fn-clear form--admin">
93+
<label>员工工资列表</label><br><br>
94+
<div id="salaryList"></div>
95+
<button type="button" onclick="SalaryUtil.addEmployeeRow()" class="green" style="width:100%;margin:10px 0;">+ 添加员工</button>
96+
<br/><br/>
97+
<button type="button" onclick="SalaryUtil.paySalary()" class="green fn-right" style="width:100%;">发放工资</button>
98+
</div>
99+
</div>
87100
</div>
101+
102+
<script>
103+
const SalaryUtil = {
104+
STORAGE_KEY: 'admin_salary_list',
105+
servePath: '${servePath}',
106+
107+
init: function() {
108+
this.loadFromStorage();
109+
},
110+
111+
addEmployeeRow: function(data) {
112+
const container = document.getElementById('salaryList');
113+
const row = document.createElement('div');
114+
row.className = 'salary-row';
115+
row.style.cssText = 'display:flex;gap:10px;margin:10px 0;align-items:center;';
116+
const userName = data ? data.userName : '';
117+
const amount = data ? data.amount : '';
118+
row.innerHTML =
119+
'<input type="text" class="salary-username" value="' + userName + '" placeholder="用户名" style="flex:1;padding:8px;" oninput="SalaryUtil.saveToStorage()">' +
120+
'<input type="number" class="salary-amount" value="' + amount + '" placeholder="工资" min="1" style="width:100px;padding:8px;" oninput="SalaryUtil.saveToStorage()">' +
121+
'<button type="button" onclick="SalaryUtil.removeEmployeeRow(this)" style="padding:8px 16px;">删除</button>';
122+
container.appendChild(row);
123+
this.saveToStorage();
124+
},
125+
126+
removeEmployeeRow: function(btn) {
127+
const row = btn.closest('.salary-row');
128+
row.remove();
129+
this.saveToStorage();
130+
},
131+
132+
saveToStorage: function() {
133+
const data = this.getEmployeeData();
134+
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
135+
},
136+
137+
loadFromStorage: function() {
138+
const stored = localStorage.getItem(this.STORAGE_KEY);
139+
if (stored) {
140+
try {
141+
const data = JSON.parse(stored);
142+
if (Array.isArray(data) && data.length > 0) {
143+
document.getElementById('salaryList').innerHTML = '';
144+
data.forEach(emp => this.addEmployeeRow(emp));
145+
return;
146+
}
147+
} catch (e) {
148+
console.error('Failed to load salary list:', e);
149+
}
150+
}
151+
// 默认添加一行
152+
this.addEmployeeRow();
153+
},
154+
155+
getEmployeeData: function() {
156+
const rows = document.querySelectorAll('.salary-row');
157+
const data = [];
158+
rows.forEach(row => {
159+
const userName = row.querySelector('.salary-username').value.trim();
160+
const amount = parseInt(row.querySelector('.salary-amount').value);
161+
if (userName && amount > 0) {
162+
data.push({ userName: userName, amount: amount });
163+
}
164+
});
165+
return data;
166+
},
167+
168+
paySalary: function() {
169+
const data = this.getEmployeeData();
170+
if (data.length === 0) {
171+
alert('请至少添加一名员工');
172+
return;
173+
}
174+
175+
if (!confirm('确认向 ' + data.length + ' 名员工发放工资?')) {
176+
return;
177+
}
178+
179+
fetch(this.servePath + '/admin/pay-salary', {
180+
method: 'POST',
181+
headers: {
182+
'Content-Type': 'application/json'
183+
},
184+
body: JSON.stringify({ employees: data })
185+
})
186+
.then(res => res.json())
187+
.then(result => {
188+
alert(result.msg + '\n\n' + result.logMessage);
189+
})
190+
.catch(err => {
191+
alert('请求失败:' + err.message);
192+
});
193+
}
194+
};
195+
196+
// 页面加载时初始化
197+
SalaryUtil.init();
198+
</script>
88199
</@admin>

0 commit comments

Comments
 (0)