Skip to content

Commit 25508c3

Browse files
committed
display diff text.
1 parent 54e92e9 commit 25508c3

File tree

3 files changed

+202
-32
lines changed

3 files changed

+202
-32
lines changed

src/EasyFrameWork/AuditTrail/FieldChange.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,24 @@ public class FieldChange
1111
public int Sequence { get; set; }
1212
public string Field { get; set; }
1313
public int? ValueType { get; set; }
14+
public int? ChangeType { get; set; }
1415
public string OldValue { get; set; }
1516
public string NewValue { get; set; }
1617
}
18+
public enum AuditChangeType
19+
{
20+
Added = 1,
21+
Updated = 2,
22+
Deleted = 3
23+
}
24+
public enum AuditValueType
25+
{
26+
String = 1,
27+
Integer = 2,
28+
Decimal = 3,
29+
DateTime = 4,
30+
Boolean = 5,
31+
Guid = 6,
32+
Html = 7
33+
}
1734
}

src/Plugins/ZKEACMS.AuditTrail/Service/EntityComparer.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ private static void CompareCollection(object oldObj,
194194
changes.Add(new FieldChange
195195
{
196196
Field = fieldName,
197-
OldValue = null,
198-
NewValue = $"{{Added}} {keyAndTitle}"
197+
ChangeType = (int)AuditChangeType.Added,
198+
NewValue = keyAndTitle
199199
});
200200
}
201201
else if (!newDict.ContainsKey(key))
@@ -206,8 +206,8 @@ private static void CompareCollection(object oldObj,
206206
changes.Add(new FieldChange
207207
{
208208
Field = fieldName,
209-
OldValue = $"{{Removed}} {keyAndTitle}",
210-
NewValue = null
209+
ChangeType = (int)AuditChangeType.Deleted,
210+
NewValue = keyAndTitle
211211
});
212212
}
213213
else
@@ -237,26 +237,26 @@ private static void CompareSimpleElements(string fieldName,
237237
var newSet = new HashSet<object>(newItems.Where(i => i != null));
238238

239239
// Find items that were added
240-
var addedItems = newSet.Except(oldSet);
240+
var addedItems = newSet.Except(oldSet).ToList();
241241
if (addedItems.Any())
242242
{
243243
changes.Add(new FieldChange
244244
{
245245
Field = fieldName,
246-
OldValue = null,
247-
NewValue = $"{{Added}} {SerializeValue(addedItems, currentPropertyInfo, valueProviders)}"
246+
ChangeType = (int)AuditChangeType.Added,
247+
NewValue = SerializeValue(addedItems, currentPropertyInfo, valueProviders)
248248
});
249249
}
250250

251251
// Find items that were removed
252-
var deletedItems = oldSet.Except(newSet);
252+
var deletedItems = oldSet.Except(newSet).ToList();
253253
if (deletedItems.Any())
254254
{
255255
changes.Add(new FieldChange
256256
{
257257
Field = fieldName,
258-
OldValue = $"{{Removed}} {SerializeValue(deletedItems, currentPropertyInfo, valueProviders)}",
259-
NewValue = null
258+
ChangeType = (int)AuditChangeType.Deleted,
259+
NewValue = SerializeValue(deletedItems, currentPropertyInfo, valueProviders)
260260
});
261261
}
262262
}

src/Plugins/ZKEACMS.AuditTrail/Views/AuditTrail/History.cshtml

Lines changed: 175 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,34 @@
1111
html body {
1212
background: #fff;
1313
}
14+
15+
.diff-added {
16+
background-color: #d4edda;
17+
color: #155724;
18+
padding: 1px 2px;
19+
border-radius: 2px;
20+
}
21+
22+
.diff-removed {
23+
background-color: #f8d7da;
24+
color: #721c24;
25+
padding: 1px 2px;
26+
border-radius: 2px;
27+
}
28+
29+
.old-value-cell,
30+
.new-value-cell {
31+
overflow: hidden;
32+
text-overflow: ellipsis;
33+
max-width: 400px;
34+
white-space: nowrap;
35+
}
36+
37+
tr.open .old-value-cell,
38+
tr.open .new-value-cell {
39+
overflow: visible;
40+
white-space: normal;
41+
}
1442
</style>
1543
}
1644
<div style="background:#fff">
@@ -29,30 +57,42 @@
2957
@foreach (var record in Model)
3058
{
3159
var changes = JsonConverter.Deserialize<Easy.AuditTrail.FieldChange[]>(record.Changes ?? "[]");
32-
if (changes.Length > 0)
60+
for (int i = 0; i < changes.Length; i++)
3361
{
34-
for (int i = 0; i < changes.Length; i++)
35-
{
36-
var change = changes[i];
37-
<tr>
38-
<td>@change.Field</td>
39-
<td class="text-muted">@change.OldValue</td>
40-
<td class="text-success">@change.NewValue</td>
41-
@if (i == 0)
62+
var change = changes[i];
63+
<tr>
64+
<td>@change.Field</td>
65+
<td>
66+
@if (change.OldValue != null)
4267
{
43-
<td rowspan="@changes.Length">@record.CreatebyName</td>
44-
<td rowspan="@changes.Length">@record.CreateDate.Value.ToString("yyyy-MM-dd HH:mm:ss")</td>
68+
<div class="old-value-cell">@change.OldValue</div>
4569
}
46-
</tr>
47-
}
48-
}
49-
else
50-
{
51-
<tr>
52-
<td colspan="3"><em>No changes</em></td>
53-
<td>@record.IPAddress</td>
54-
<td>@record.CreatebyName</td>
55-
<td>@record.CreateDate.Value.ToString("yyyy-MM-dd HH:mm:ss")</td>
70+
</td>
71+
<td>
72+
@if (change.ChangeType == (int)Easy.AuditTrail.AuditChangeType.Added)
73+
{
74+
<span class="label label-success">
75+
<code class="small">@L("Add")</code>
76+
<span class="text-success">@change.NewValue</span>
77+
</span>
78+
}
79+
else if (change.ChangeType == (int)Easy.AuditTrail.AuditChangeType.Deleted)
80+
{
81+
<span class="label label-danger">
82+
<code class="small">@L("Delete")</code>
83+
<del class="text-danger">@change.NewValue</del>
84+
</span>
85+
}
86+
else
87+
{
88+
<div class="new-value-cell">@change.NewValue</div>
89+
}
90+
</td>
91+
@if (i == 0)
92+
{
93+
<td rowspan="@changes.Length">@record.CreatebyName</td>
94+
<td rowspan="@changes.Length">@record.CreateDate.Value.ToString("yyyy-MM-dd HH:mm:ss")</td>
95+
}
5696
</tr>
5797
}
5898
}
@@ -63,4 +103,117 @@
63103
{
64104
await Html.Pagin((ZKEACMS.Pagin)ViewBag.Pagin);
65105
}
66-
</div>
106+
</div>
107+
@using (Script.AtFoot())
108+
{
109+
<script type="text/javascript">
110+
function diffText(oldText, newText) {
111+
const oldTokens = tokenizeText(oldText);
112+
const newTokens = tokenizeText(newText);
113+
114+
const m = oldTokens.length;
115+
const n = newTokens.length;
116+
117+
const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
118+
119+
for (let i = 1; i <= m; i++) {
120+
for (let j = 1; j <= n; j++) {
121+
if (oldTokens[i - 1] === newTokens[j - 1]) {
122+
dp[i][j] = dp[i - 1][j - 1] + 1;
123+
} else {
124+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
125+
}
126+
}
127+
}
128+
129+
let i = m, j = n;
130+
const oldResult = [];
131+
const newResult = [];
132+
133+
while (i > 0 || j > 0) {
134+
if (i > 0 && j > 0 && oldTokens[i - 1] === newTokens[j - 1]) {
135+
oldResult.unshift({ text: oldTokens[i - 1], type: 'same' });
136+
newResult.unshift({ text: newTokens[j - 1], type: 'same' });
137+
i--;
138+
j--;
139+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
140+
newResult.unshift({ text: newTokens[j - 1], type: 'added' });
141+
j--;
142+
} else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
143+
oldResult.unshift({ text: oldTokens[i - 1], type: 'removed' });
144+
i--;
145+
}
146+
}
147+
148+
return {
149+
oldResult: mergeAdjacentTokens(oldResult),
150+
newResult: mergeAdjacentTokens(newResult)
151+
};
152+
}
153+
154+
function tokenizeText(text) {
155+
const tokenRegex = /(\s+|[^\w\s]|\w+)/g;
156+
const tokens = [];
157+
let match;
158+
159+
while ((match = tokenRegex.exec(text)) !== null) {
160+
tokens.push(match[0]);
161+
}
162+
163+
return tokens;
164+
}
165+
166+
function mergeAdjacentTokens(tokens) {
167+
if (tokens.length === 0) return [];
168+
169+
const result = [];
170+
let currentToken = { ...tokens[0] };
171+
172+
for (let i = 1; i < tokens.length; i++) {
173+
if (tokens[i].type === currentToken.type) {
174+
currentToken.text += tokens[i].text;
175+
} else {
176+
result.push(currentToken);
177+
currentToken = { ...tokens[i] };
178+
}
179+
}
180+
181+
result.push(currentToken);
182+
return result;
183+
}
184+
185+
function tokensToHtml(tokens) {
186+
return tokens.map(token => {
187+
let className = '';
188+
switch (token.type) {
189+
case 'added':
190+
return `<span class="diff-added">${token.text}</span>`;
191+
case 'removed':
192+
return `<span class="diff-removed">${token.text}</span>`;
193+
case 'same':
194+
return `<span>${token.text}</span>`;
195+
default:
196+
return `<span>${token.text}</span>`;
197+
}
198+
}).join('');
199+
}
200+
201+
$(function () {
202+
$("tr").click(function () {
203+
$(this).toggleClass("open");
204+
});
205+
$('tr').each(function () {
206+
const oldValueCell = $(this).find('.old-value-cell');
207+
const newValueCell = $(this).find('.new-value-cell');
208+
209+
const oldText = $.trim(oldValueCell.html());
210+
const newText = $.trim(newValueCell.html());
211+
if (oldText && newText) {
212+
const diffResult = diffText(oldText, newText);
213+
newValueCell.html(tokensToHtml(diffResult.newResult));
214+
oldValueCell.html(tokensToHtml(diffResult.oldResult));
215+
}
216+
});
217+
});
218+
</script>
219+
}

0 commit comments

Comments
 (0)