chore: 🔨 框架代码同步 (#343)

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
2025-08-02 17:33:13 +08:00
committed by GitHub
parent 1650b8a127
commit 041f3eeda1
25 changed files with 184 additions and 83 deletions

View File

@ -41,7 +41,6 @@ USDT
响应体
响应状态码
唯一编码
处理中
备注
外国人居留证
外部错误
@ -59,14 +58,12 @@ USDT
已冻结
已发送
已婚
已完成
已校验
已解冻
已读
并且
归属角色
归属部门
待执行
微信支付
成功
或者
@ -101,7 +98,6 @@ USDT
最后登录时间
未处理异常
未婚
未结算
未读
本人数据
本科
@ -116,7 +112,6 @@ USDT
消息类型
港澳台通行证
用户代理
用户取消
用户名
用户导出
用户邀请导出
@ -133,6 +128,7 @@ USDT
等于
等待发送
等待支付
管理员充值
管理员扣费
管理员赠送
管理模块

View File

@ -61,6 +61,7 @@ XML注释文件不存在
未指定部门
未获取到待执行任务
档案可见性不正确
此操作不被允许
此节点已下线
民族不正确
消息主题不能为空
@ -100,7 +101,6 @@ XML注释文件不存在
请求地址不能为空
请求对象不能为空
请求方法不正确
请稍后重试
请联系管理员激活账号
读取用户令牌出错
账号不能为空
@ -116,7 +116,6 @@ XML注释文件不存在
随机延时结束时间不正确
随机延时起始时间不正确
非JSON字符串
此操作不被允许
验证数据不能为空
验证码不正确
验证码不能为空

View File

@ -1 +0,0 @@
示例导出

View File

@ -11,7 +11,7 @@ public enum AdminTradeTypes
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.管理员赠送))]
[Trade(Direction = TradeDirections.Income)]
AdminDeposit = 1
AdminGift = 1
,
@ -21,4 +21,13 @@ public enum AdminTradeTypes
[ResourceDescription<Ln>(nameof(Ln.管理员扣费))]
[Trade(Direction = TradeDirections.Expense)]
AdminDeduct = 2
,
/// <summary>
/// 管理员充值
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.管理员充值))]
[Trade(Direction = TradeDirections.Income)]
AdminDeposit = 6
}

View File

@ -11,7 +11,7 @@ public enum TradeTypes
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.管理员赠送))]
[Trade(Direction = TradeDirections.Income)]
AdminDeposit = 1
AdminGift = 1
,
@ -47,5 +47,12 @@ public enum TradeTypes
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.转账收入))]
[Trade(Direction = TradeDirections.Income)]
TransferIncome = 5
TransferIncome = 5,
/// <summary>
/// 管理员充值
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.管理员充值))]
[Trade(Direction = TradeDirections.Income)]
AdminDeposit = 6
}

View File

@ -3,13 +3,13 @@
<Import Project="$(SolutionDir)/build/copy.pkg.xml.comment.files.targets"/>
<Import Project="$(SolutionDir)/build/prebuild.targets"/>
<ItemGroup>
<PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.7" Label="refs"/>
<PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.7" Label="refs"/>
<PackageReference Include="Gurion" Version="1.2.16" Label="refs"/>
<PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.8" Label="refs"/>
<PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.8" Label="refs"/>
<PackageReference Include="Gurion" Version="1.2.17" Label="refs"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.7"/>
<PackageReference Include="Minio" Version="6.0.5"/>
<PackageReference Include="NSExt" Version="2.3.7"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6"/>
<PackageReference Include="NSExt" Version="2.3.8"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7"/>
</ItemGroup>
<ItemGroup>
<!--<ProjectReference Include="$(SolutionDir)/refs/NetAdmin.FreeSql/src/FreeSql.DbContext/FreeSql.DbContext.csproj" Label="refs"/>-->

View File

@ -147,6 +147,11 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
throw new NetAdminInvalidOperationException(Ln.);
}
// 不允许自己对自己转账
if (UserToken.Id == fromAccount.Id) {
throw new NetAdminInvalidOperationException(Ln.);
}
var fromUser = await S<IUserService>().GetAsync(new QueryUserReq { Id = fromAccount.Id }).ConfigureAwait(false);
// 源账户扣钱
@ -178,6 +183,11 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
{
var toUser = await S<IUserService>().GetAsync(new QueryUserReq { Id = req.OwnerId!.Value }).ConfigureAwait(false);
// 不允许自己对自己转账
if (UserToken.Id == toUser.Id) {
throw new NetAdminInvalidOperationException(Ln.);
}
// 自己账户扣钱
_ = await CreateAsync(new CreateWalletTradeReq {
Amount = -req.Amount

View File

@ -99,7 +99,6 @@ public sealed class UserWalletController(IUserWalletCache cache) : ControllerBas
/// <summary>
/// 用户钱包求和
/// </summary>
[NonAction]
public Task<decimal> SumAsync(QueryReq<QueryUserWalletReq> req)
{
return Cache.SumAsync(req);

View File

@ -81,4 +81,15 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 用户钱包求和
*/
sum: {
url: `${config.API_URL}/api/sys/user.wallet/sum`,
name: `用户钱包求和`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -29,18 +29,39 @@
:disabled="item.disabled?.includes(mode)"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss" />
<el-select v-else-if="item.enum" v-model="form[i]" :disabled="item.disabled?.includes(mode)" clearable filterable>
<el-option
v-for="e in Object.entries(this.$GLOBAL.enums[item.enum]).map((x) => {
return {
value: x[0],
text: item.enumSelectText ? item.enumSelectText(x) : item.enumText ? item.enumText(x) : x[1][1],
}
})"
:key="e.value"
:label="e.text"
:value="e.value" />
</el-select>
<template v-else-if="item.enum">
<el-radio-group v-if="item.enum.radio" v-model="form[i]" :disabled="item.disabled?.includes(mode)">
<el-radio-button
v-for="e in Object.entries(this.$GLOBAL.enums[item.enum.name]).map((x) => {
return {
value: x[0],
text: item.enum.selectText
? item.enum.selectText(x)
: item.enum.text
? item.enum.text(x)
: x[1][1],
}
})"
:label="e.text"
:value="e.value" />
</el-radio-group>
<el-select v-else="item.enum" v-model="form[i]" :disabled="item.disabled?.includes(mode)" clearable filterable>
<el-option
v-for="e in Object.entries(this.$GLOBAL.enums[item.enum.name]).map((x) => {
return {
value: x[0],
text: item.enum.selectText
? item.enum.selectText(x)
: item.enum.text
? item.enum.text(x)
: x[1][1],
}
})"
:key="e.value"
:label="e.text"
:value="e.value" />
</el-select>
</template>
<el-switch
v-else-if="typeof form[i] === `boolean` || item.isSwitch"
v-model="form[i]"
@ -63,6 +84,13 @@
v-model="form[i]"
:disabled="item.disabled?.includes(mode)"
:is="item.detail?.is ?? `el-input`" />
<component
v-bind="sub.props"
v-for="(sub, j) in item.detail.extra"
v-html="sub.html"
v-if="item.detail?.extra"
:is="sub.is"
:key="j" />
</el-form-item>
</template>
</el-form>

View File

@ -3,12 +3,15 @@
<!-- 仪表板-->
<el-header v-loading="statistics.total === `...`" class="el-header-statistics">
<el-row :gutter="15">
<el-col :lg="Object.keys(statistics.sumList).length === 0 ? 24 : 24 / (Object.keys(statistics.sumList).length + 1)">
<el-col :lg="Math.floor(Object.keys(statistics.sumList).length === 0 ? 24 : 24 / (Object.keys(statistics.sumList).length + 1))">
<el-card shadow="never">
<sc-statistic :title="this.$t(totalCountLabel)" :value="statistics.total" group-separator />
</el-card>
</el-col>
<el-col v-for="(item, i) in Object.entries(statistics.sumList)" :key="i" :lg="24 / (Object.keys(statistics.sumList).length + 1)">
<el-col
v-for="(item, i) in Object.entries(statistics.sumList).sort((x, y) => x[1][3] - y[1][3])"
:key="i"
:lg="Math.floor(24 / (Object.keys(statistics.sumList).length + 1)) + (item[1][2] ?? 0)">
<el-card shadow="never">
<sc-statistic :prefix="item[1][1]" :title="item[0]" :value="item[1][0]" group-separator />
</el-card>
@ -55,9 +58,20 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button v-bind="rightButtons[0].props" v-else-if="rightButtons.length === 1" @click="rightButtons[0].click" type="primary">{{
rightButtons[0].label
}}</el-button>
<el-button
v-bind="rightButtons[0].props"
v-else-if="rightButtons.length === 1"
:disabled="loading"
:loading="loading"
@click="
async () => {
this.loading = true
await rightButtons[0].click()
this.loading = false
}
"
>{{ rightButtons[0].label }}</el-button
>
</div>
</el-header>
@ -68,7 +82,9 @@
Object.assign(
{
queryApi: $API[entityName].pagedQuery,
exportApi: $API[entityName].export,
exportApi: $GLOBAL.hasApiPermission($API[entityName].export?.url.replace(`${config.API_URL}/`, ``))
? $API[entityName].export
: null,
remoteFilter: true,
remoteSort: true,
rowKey: `id`,
@ -100,10 +116,10 @@
:options="
item.options ??
(item.enum
? Object.entries(this.$GLOBAL.enums[item.enum]).map((x) => {
? Object.entries(this.$GLOBAL.enums[item.enum.name]).map((x) => {
return {
value: x[0],
text: item.enumText ? item.enumText(x) : x[1][1],
text: item.enum.text ? item.enum.text(x) : x[1][1],
type: x[1][2],
pulse: x[1][3] === `true`,
}
@ -127,8 +143,12 @@
</span>
</template>
<template v-else-if="item.currency" #default="{ row }">
<span>{{ item.currency }}</span>
<span>{{ $TOOL.groupSeparator((row[i] / 100).toFixed(2)) }}</span>
<p :class="{ expense: row[i] < 0 }">
<span>{{ item.currency.flag }}</span>
<span>{{
$TOOL.groupSeparator((row[i] / (item.currency.multiple ?? 100)).toFixed(item.currency.decimal ?? 2))
}}</span>
</p>
</template>
</component>
</template>
@ -200,12 +220,20 @@ export default {
},
created() {
for (const f of this.dyFilters) {
this.query.dynamicFilter.filters.push(f)
if (f.operator === 'dateRange') {
this.query.dynamicFilter.filters.push({
field: f.field,
operator: f.operator,
value: f.value.map((x) => x.replace(/ 00:00:00$/, '')),
})
} else this.query.dynamicFilter.filters.push(f)
}
const searchFields = []
this.searchControls = []
for (const item in this.columns) {
this.resetSumList(item)
this.table.menu.extra[item] = this.columns[item].extra
if (this.columns[item].searchable) {
searchFields.push({ label: this.columns[item].label, key: item })
@ -215,8 +243,8 @@ export default {
this.searchControls.push({
type: `select-input`,
field: [`dy`, searchFields],
placeholder: this.$t(`匹配内容`),
style: `width:25rem`,
placeholder: this.$t(`检索内容`),
style: `width:${this.searchInputWidth}rem`,
selectStyle: `width:8rem`,
})
}
@ -238,7 +266,7 @@ export default {
{ value: true, label: this.$t(``) },
{ value: false, label: this.$t(``) },
]
: Object.entries(this.$GLOBAL.enums[col.enum]).map((x) => {
: Object.entries(this.$GLOBAL.enums[col.enum.name]).map((x) => {
return {
value: x[0],
label: x[1][1],
@ -307,6 +335,11 @@ export default {
},
inject: [`reload`],
methods: {
resetSumList(item) {
if (this.columns[item].sum && (!this.columns[item].condition || this.columns[item].condition())) {
this.statistics.sumList[this.columns[item].sum.label] = ['...', '', this.columns[item].sum.span, this.columns[item].sum.sort ?? 0]
}
},
// ---------------------------- ↓ 过滤器事件 ----------------------------
onFilterChange(data) {
Object.entries(data).forEach(([key, value]) => {
@ -321,35 +354,24 @@ export default {
Object.entries(this.$refs.selectFilter?.selected ?? []).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = [``]))
},
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: `createdTime`,
operator: `dateRange`,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
for (const item in this.columns) {
this.resetSumList(item)
let field = form.dy[item]
if (field === undefined) field = form.dy[item[0].toUpperCase() + item.substring(1)]
if (field === undefined || field === `` || typeof field === `object`) {
if (field === undefined || field === `` || field == null) {
continue
}
const operator = this.columns[item].operator ?? `eq`
this.query.dynamicFilter.filters.push({
field: item,
operator: this.columns[item].operator ?? `eq`,
value: field,
operator: operator,
value: operator === 'dateRange' ? field.map((x) => x.replace(/ 00:00:00$/, '')) : field,
})
}
for (const item of this.dyFilters) {
const exists = this.query.dynamicFilter.filters.find((x) => x.field === item.field)
if (!exists) {
this.query.dynamicFilter.filters.push(item)
}
}
await this.$refs.table.upData()
},
// ---------------------------- 搜索栏事件 ↑ ----------------------------
@ -438,18 +460,24 @@ export default {
}),
)
}
if (this.columns[col].sum) {
if (this.columns[col].sum && (!this.columns[col].condition || this.columns[col].condition())) {
const res = await this.$API[this.entityName].sum.post({
filter: this.query.filter,
dynamicFilter: { filters: this.query.dynamicFilter.filters },
requiredFields: [col.replace(/(?:^|\.)[a-z]/g, (m) => m.toUpperCase())],
})
this.statistics.sumList[this.columns[col].sum.label] = [
this.columns[col].sum.currency ? (res.data / 100).toFixed(2) : res.data,
this.columns[col].sum.currency,
this.columns[col].sum.currency
? (res.data / (this.columns[col].sum.currency.multiple ?? 100)).toFixed(this.columns[col].sum.currency.decimal ?? 2)
: res.data,
this.columns[col].sum.currency?.flag,
this.columns[col].sum.span,
this.columns[col].sum.sort ?? 0,
]
}
}
const res = await Promise.all(countByCalls)
Object.assign(this.statistics, { res })
for (const item of res) {
@ -490,7 +518,9 @@ export default {
}
for (const f of this.dyFilters) {
this.$refs.search.selectInputKey = f.field
if (this.searchControls.findIndex((x) => x.field[1] === f.field) >= 0) {
this.$refs.search.selectInputKey = f.field
}
this.$refs.search.form.dy[f.field] = f.value
this.$refs.search.keeps.push({
field: f.field,
@ -507,6 +537,7 @@ export default {
summary: { type: String },
selectFilters: { type: Array, default: [] },
customSearchControls: { type: Array, default: [] },
searchInputWidth: { type: Number, default: 25 },
columns: { type: Object },
operations: { type: Array, default: [`view`, `add`, `edit`, `del`] },
rowButtons: { type: Array, default: [] },
@ -522,4 +553,8 @@ export default {
}
</script>
<style scoped />
<style scoped>
.expense {
color: var(--el-color-danger);
}
</style>

View File

@ -7,10 +7,10 @@
</div>
<el-select v-bind="$attrs" :allow-create="allowCreate" :loading="loading" @visible-change="visibleChange">
<el-option
v-for="item in options"
:key="$TOOL.getNestedProperty(item, props.value)"
v-for="(item, i) in options"
:key="i"
:label="typeof props.label === `function` ? props.label(item) : $TOOL.getNestedProperty(item, props.label)"
:value="objValueType ? item : $TOOL.getNestedProperty(item, props.value)">
:value="typeof props.value === `function` ? props.value(item) : $TOOL.getNestedProperty(item, props.value)">
<slot :data="item" name="option" />
</el-option>
</el-select>
@ -27,7 +27,6 @@ export default {
default: () => {},
},
dic: { type: String, default: '' },
objValueType: { type: Boolean, default: false },
allowCreate: { type: Boolean, default: false },
params: { type: Object, default: () => ({}) },
config: { type: Object },

View File

@ -110,7 +110,7 @@ export default {
},
mounted() {
this.defaultProps = Object.assign(this.defaultProps, this.props)
this.defaultValue = this.modelValue
this.defaultValue = this.multiple ? this.modelValue || [] : this.modelValue
this.autoCurrentLabel()
},
methods: {
@ -233,7 +233,9 @@ export default {
//tags删除后回调
removeTag(tag) {
const row = this.findRowByKey(tag[this.defaultProps.value])
this.$refs.table.toggleRowSelection(row, false)
if (row) {
this.$refs.table.toggleRowSelection(row, false)
}
this.$emit('update:modelValue', this.defaultValue)
},
//清空后的回调

View File

@ -504,7 +504,9 @@ textarea {
.mt-8 {
margin-top: 2rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mb-8 {
margin-bottom: 2rem;
}

View File

@ -1,6 +1,6 @@
<template>
<el-card :header="$t('登录日志')" shadow="never">
<login-log :ownerId="$GLOBAL.user.id.toString()" :show-filter="false" />
<login-log :owner-id="$GLOBAL.user.id.toString()" :show-filter="false" />
</el-card>
</template>

View File

@ -21,7 +21,7 @@
gender: {
label: $t(`性别`),
is: `na-col-indicator`,
enum: `genders`,
enum: { name: `genders` },
width: 100,
align: `center`,
countBy: true,
@ -56,6 +56,7 @@
createdTime: {
label: $t(`创建时间`),
show: [`view`],
operator: `dateRange`,
},
version: {
label: $t(`数据版本`),

View File

@ -62,7 +62,7 @@
{ label: $t('充值点数'), key: 'depositPoint' },
],
],
placeholder: $t('匹配内容'),
placeholder: $t('检索内容'),
style: 'width:25rem',
selectStyle: 'width:8rem',
},

View File

@ -51,7 +51,7 @@
},
status: {
is: `na-col-indicator`,
enum: `walletFrozenStatues`,
enum: { name: `walletFrozenStatues` },
label: $t(`状态`),
countBy: true,
align: `center`,
@ -64,7 +64,7 @@
},
countBy: true,
is: `na-col-indicator`,
enum: `walletFrozenReasons`,
enum: { name: `walletFrozenReasons` },
label: $t(`冻结原因`),
align: `center`,
width: 100,
@ -84,6 +84,7 @@
createdTime: {
label: $t(`创建时间`),
show: [`view`],
operator: `dateRange`,
},
version: {
label: $t(`数据版本`),

View File

@ -42,7 +42,7 @@
},
countBy: true,
is: `na-col-indicator`,
enum: `tradeDirections`,
enum: { name: `tradeDirections` },
label: $t(`交易方向`),
align: `center`,
width: 100,
@ -54,7 +54,7 @@
},
countBy: true,
is: `na-col-indicator`,
enum: `tradeTypes`,
enum: { name: `tradeTypes` },
label: $t(`交易类型`),
align: `center`,
width: 150,
@ -100,6 +100,7 @@
createdTime: {
label: $t(`创建时间`),
show: [`view`],
operator: `dateRange`,
},
createdUserId: {
headerAlign: `center`,

View File

@ -57,6 +57,7 @@
createdTime: {
label: $t(`创建时间`),
show: [`view`],
operator: `dateRange`,
},
modifiedTime: {
relativeTime: true,

View File

@ -84,7 +84,7 @@
{ label: $t('客户端IP'), key: 'createdClientIp' },
],
],
placeholder: $t('匹配内容'),
placeholder: $t('检索内容'),
style: 'width:25rem',
selectStyle: 'width:8rem',
},

View File

@ -101,7 +101,7 @@
{ label: '客户端IP', key: 'createdClientIp' },
],
],
placeholder: '匹配内容',
placeholder: '检索内容',
style: 'width:25rem',
selectStyle: 'width:8rem',
},

View File

@ -27,6 +27,7 @@
align: `right`,
show: [`list`],
width: 170,
operator: `dateRange`,
},
ownerId: {
headerAlign: `center`,

View File

@ -241,7 +241,7 @@
<wallet v-if="tabId === 'wallet'" :id="form.id.toString()" />
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('交易流水')" name="trade">
<trade v-if="tabId === 'trade'" :ownerId="form.id.toString()" />
<trade v-if="tabId === 'trade'" :owner-id="form.id.toString()" />
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('操作日志')" name="log">
<log v-if="tabId === 'log'" :owner-id="form.id" />

View File

@ -72,7 +72,7 @@
{ label: '作业编号', key: 'jobId' },
],
],
placeholder: '匹配内容',
placeholder: '检索内容',
style: 'width:25rem',
selectStyle: 'width:8rem',
},