Browse Source

1.异步策略调整: async setup => onMounted async 2.json配置动态方法禁用按钮 disabled string => function 3.Auth项目配置文件修复,Swagger Token认证调整 4.登录方法包装,添加用户详情缺失的的角色列表

master
wanggang 1 year ago
parent
commit
e81cd0f265
  1. 4
      code/.gitignore
  2. 5
      code/WebApp/vanilla/app.js
  3. 45
      code/WebApp/vanilla/components/form/form-input.js
  4. 20
      code/WebApp/vanilla/components/form/form-item.js
  5. 2
      code/WebApp/vanilla/components/icon/index.js
  6. 119
      code/WebApp/vanilla/components/list/index.js
  7. 1
      code/WebApp/vanilla/config/settings.js
  8. 2
      code/WebApp/vanilla/models/login.js
  9. 53
      code/WebApp/vanilla/models/user.js
  10. 23
      code/WebApp/vanilla/request/index.js
  11. 3
      code/WebApp/vanilla/router/routes.js
  12. 25
      code/WebApp/vanilla/utils/index.js
  13. 113
      code/WebApp/vanilla/utils/validation.js
  14. 15
      code/WebApp/vanilla/views/login.js
  15. 14
      code/src/Modules/BaseService/BaseService.Application.Contracts/Systems/UserManagement/IUserAppService.cs
  16. 2
      code/src/Modules/BaseService/BaseService.Application/BaseService.Application.csproj
  17. 55
      code/src/Modules/BaseService/BaseService.Application/UserManagement/TokenAppService.cs
  18. 21
      code/src/Modules/BaseService/BaseService.Application/UserManagement/UserAppService.cs
  19. 12
      code/src/Modules/BaseService/BaseService.Host/.config/dotnet-tools.json
  20. 72
      code/src/Modules/BaseService/BaseService.Host/BaseServiceHostModule.cs
  21. 20
      code/src/Modules/BaseService/BaseService.Host/appsettings.json
  22. BIN
      docs/20230711-151906.jpg
  23. 1021
      docs/SqlSchemaCompare2.scmp

4
code/.gitignore

@ -26,8 +26,8 @@ dist/
#be
.vs/
bin/
obj/
bin
obj
*.suo
*.user
*.db

5
code/WebApp/vanilla/app.js

@ -6,12 +6,11 @@ import { Suspense, reactive, onMounted } from "vue";
export default {
components: { ElConfigProvider, Suspense },
template: html`<suspense>
template: html`
<el-config-provider :locale="localeMap.get($i18n.locale)">
<router-view></router-view>
</el-config-provider>
<template #fallback> Loading... </template>
</suspense>`,
`,
setup() {
const localeMap = reactive(
new Map([

45
code/WebApp/vanilla/components/form/form-input.js

@ -1,7 +1,7 @@
import html from "html";
import { ref, reactive, watch } from "vue";
import { ref, reactive, watch, onMounted } from "vue";
import { dayjs } from "element-plus";
import { post } from "../../request/index.js";
import request, { post } from "../../request/index.js";
export default {
template: html`
@ -55,28 +55,25 @@ export default {
v-model="model[prop]"
type="password"
show-password
v-if="schema.format==='password'"
v-if="schema.input==='password'"
/>
<el-input :disabled="getDisabled()" :placeholder="schema.title" v-model="model[prop]" type="text" v-else />
</template>
</template>
`,
props: ["modelValue", "schema", "prop", "isReadOnly"],
props: ["modelValue", "schema", "prop", "isReadOnly", "mode"],
emit: ["update:modelValue"],
async setup(props, context) {
setup(props, context) {
const model = reactive(props.modelValue);
watch(model, (value) => {
context.emit("update:modelValue", value);
});
/*start*/
const getDisabled = () => {
if (props.isReadOnly && props.isReadOnly === true) {
if (props.mode==='details') {
return true;
}
if (props.schema.displayOnly) {
return true;
}
if (props.mode === "update" && props.schema.addOnly) {
if (props.mode === "update" && props.schema.readOnly) {
return true;
}
return false;
@ -89,20 +86,22 @@ export default {
const selectProps = ref({});
const selectValues = ref([]);
const options = ref([]);
if (props.schema.options) {
options.value = props.schema.options;
} else if (props.schema.url) {
try {
const url = `${props.schema.url}`;
const result = await post(url, { queryAll: true, query: { isReadonly: null, isDisabled: null, order: null } });
options.value = result.data?.items.map((o) => ({
value: o[props.schema.value],
label: o[props.schema.label],
}));
} catch (error) {
console.log(error);
onMounted(async () => {
if (props.schema.options) {
options.value = props.schema.options;
} else if (props.schema.url) {
try {
const url = `${props.schema.url}`;
const result = await request(url, null, { method: "get" });
options.value = result.data?.items.map((o) => ({
value: o[props.schema.value],
label: o[props.schema.label],
}));
} catch (error) {
console.log(error);
}
}
}
});
return {
model,
getDisabled,

20
code/WebApp/vanilla/components/form/form-item.js

@ -1,11 +1,13 @@
import html from "html";
import { defineAsyncComponent, ref, reactive, watch } from "vue";
import { format } from "../../utils/index.js";
import { messages } from "../../utils/validation.js";
export default {
name: "formItem",
components: { AppFormInput: defineAsyncComponent(() => import("./form-input.js")) },
template: html`
<template v-if="showItem()">
<template v-if="!schema.hidden">
<template v-if="schema.type==='object'"></template>
<template v-else-if="schema.type!=='array'||schema.items.type!=='array'">
<el-form-item
@ -15,14 +17,14 @@ export default {
:rules="getRules(parentSchema,schema,model)"
:error="mode==='query'?null:getError(prop)"
>
<app-form-input :schema="schema" :prop="prop" v-model="model" :isReadOnly="mode==='details'" />
<app-form-input :schema="schema" :prop="prop" v-model="model" :mode="mode" />
</el-form-item>
</template>
</template>
`,
props: ["modelValue", "mode", "parentSchema", "schema", "prop", "errors"],
emit: ["update:modelValue"],
async setup(props, context) {
setup(props, context) {
const model = reactive(props.modelValue);
watch(model, (value) => {
context.emit("update:modelValue", value);
@ -63,18 +65,18 @@ export default {
}
if (!rule.message) {
if (rule.required) {
rule.message = format(schema.messages.required, property.title);
rule.message = format(messages.required, property.title);
} else if (rule.pattern) {
rule.message = format(schema.messages.pattern, property.title);
rule.message = format(messages.pattern, property.title);
} else if (property.type === "string" || property.type === "number" || property.type === "array") {
if (rule.len) {
rule.message = format(schema.messages[property.type].len, property.title, rule.len);
rule.message = format(messages[property.type].len, property.title, rule.len);
} else if (rule.min) {
rule.message = format(schema.messages[property.type].min, property.title, rule.min);
rule.message = format(messages[property.type].min, property.title, rule.min);
} else if (rule.max) {
rule.message = format(schema.messages[property.type].max, property.title, rule.max);
rule.message = format(messages[property.type].max, property.title, rule.max);
} else if (rule.range) {
rule.message = format(schema.messages[property.type].range, property.title, rule.range);
rule.message = format(messages[property.type].range, property.title, rule.range);
}
}
}

2
code/WebApp/vanilla/components/icon/index.js

@ -14,7 +14,7 @@ export default {
default: "file",
},
},
async setup(props) {
setup(props) {
const svg = ref(null);
onMounted(async () => {
if (!props.name.startsWith("ep-")) {

119
code/WebApp/vanilla/components/list/index.js

@ -3,10 +3,11 @@ import request, { get, post } from "../../request/index.js";
import { defineAsyncComponent, ref, reactive, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { listToTree, schemaToModel } from "../../utils/index.js";
import { listToTree, schemaToModel, importFunction } from "../../utils/index.js";
import qs from "../../lib/qs/shim.js";
import VueOfficeExcel from "@vue-office/excel";
import { camelCase, capitalize } from "lodash";
import { useAppStore } from "../../store/index.js";
export default {
name: "AppList",
@ -40,6 +41,7 @@ export default {
:class="item.meta.htmlClass??'el-button--primary'"
v-if="item.meta.isTop"
@click="click(item,selectedRows)"
:disabled="item.meta.disabled && item.meta.disabled(selectedRows)"
>
<el-icon v-if="item.meta.icon"><svg-icon :name="item.meta.icon" /></el-icon>
<span>{{item.meta.title}}</span>
@ -68,7 +70,7 @@ export default {
border
fit
>
<el-table-column fixed="left" type="selection" />
<el-table-column fixed="left" type="selection" :selectable="config.table.selectable" />
<el-table-column type="index" :label="$t('rowIndex')">
<template #default="scope">
{{ (pageModel.pageIndex - 1) * pageModel.pageSize + scope.$index + 1 }}
@ -89,7 +91,7 @@ export default {
<el-table-column :prop="key" :label="item.title">
<template #default="scope">
<el-link type="primary" @click="showList(scope.row[key],item.oneToMany)">
<app-form-input :isReadOnly="true" :schema="item" :prop="key" v-model="scope.row" />
<app-form-input mode="details" :schema="item" :prop="key" v-model="scope.row" />
</el-link>
</template>
</el-table-column>
@ -99,7 +101,7 @@ export default {
<el-table-column :prop="key" sortable="custom" :sort-orders="['descending', 'ascending', null]">
<template #header="scope">{{item.title}}</template>
<template #default="scope">
<app-form-input :isReadOnly="true" :schema="item" :prop="key" v-model="scope.row" />
<app-form-input mode="details" :schema="item" :prop="key" v-model="scope.row" />
</template>
</el-table-column>
</template>
@ -120,6 +122,7 @@ export default {
:class="item.meta.htmlClass??'el-button--primary'"
v-if="!item.meta.isTop"
@click="click(item,[scope.row])"
:disabled="item.meta.disabled && item.meta.disabled(scope.row)"
>
<el-icon v-if="item.meta.icon"><svg-icon :name="item.meta.icon" /></el-icon>
<span>{{item.meta.title}}</span>
@ -136,7 +139,7 @@ export default {
<el-row>
<el-col>
<el-pagination
v-if="tableData.length&&pageModel.pageSize<pageModel.totalCount"
v-if="pageModel.pageSize<pageModel.total"
v-model:currentPage="pageModel.pageIndex"
v-model:page-size="pageModel.pageSize"
:total="pageModel.total"
@ -211,9 +214,9 @@ export default {
inline
label-position="left"
:hideButton="true"
:schema="editFormSchema"
:schema="config.edit.schema"
v-model="editFormModel"
v-if="editFormSchema&&editFormMode"
style="height:100%;"
/>
</template>
<template v-else-if="editFormMode==='export'">
@ -321,13 +324,13 @@ export default {
</style>`,
props: ["modelValue", "config", "querySchema", "controller", "query", "buttons"],
emits: ["command"],
async setup(props, context) {
// 变量定义
//// 配置
const config = ref(props.config);
//// 分页
setup(props, context) {
/*变量定义*/
// 配置
const config = reactive(props.config);
// 分页
const pageModel = reactive({
sizeList: [20, 50, 100],
sizeList: [1, 50, 100],
pageIndex: 1,
pageSize: 10,
total: 0,
@ -348,7 +351,8 @@ export default {
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const buttons = ref(props.buttons ?? route.meta.children);
const appStore = useAppStore();
const buttons = ref(props.buttons ?? route.meta.children.filter((o) => o.meta.hasPermission));
const baseUrl = props.controller ?? `${route.meta.path}`;
const indexUrl = props.indexUrl ?? `${baseUrl}/index`;
const queryModel = ref({});
@ -405,7 +409,7 @@ export default {
queryModel.value.orderBy = Array.from(sortColumns.value)
.map((o) => capitalize(o[0]) + (o[1] === "ascending" ? "" : ` DESC`))
.join(",");
await load(indexUrl);
await load();
};
const showColumn = (item, prop) => {
return (
@ -417,10 +421,16 @@ export default {
};
const handleSelectionChange = (rows) => (selectedRows.value = rows);
const load = async (url) => {
const load = async () => {
tableLoading.value = true;
try {
const url = config.value.query.url;
const url = config.query.url;
const method = config.query.method;
//
queryModel.value = schemaToModel(config.query.schema);
queryModel.value.maxResultCount = pageModel.pageSize;
queryModel.value.skipCount = (pageModel.pageIndex - 1) * pageModel.pageSize;
//
const postData = JSON.parse(JSON.stringify(queryModel.value));
postData.filters = queryList.value.filter((o) => o.property && o.value);
if (postData.items) {
@ -429,60 +439,61 @@ export default {
if (postData.query?.id) {
delete postData.query["id"];
}
const listData = (await request(url, postData, { method: config.value.query.method.toUpperCase() })).data;
const listData = (await request(url, postData, { method })).data;
const items = listData.items;
if (tableSchema.value.isTree) {
items = listToTree(listData.items);
}
tableData.value = items;
pageModel.total = listData.totalCount;
//data.value = listData;
queryModel.tableKey.value = !tableKey.value;
tableKey.value = !tableKey.value;
} catch (error) {
console.log(error);
} finally {
tableLoading.value = false;
}
};
const onPageIndexChange = async () => {
await load(indexUrl);
};
const onPageSizeChange = async () => await load(indexUrl);
const onPageIndexChange = async () => await load();
const onPageSizeChange = async () => await load();
const click = async (item, rows) => {
editFormloading.value = true;
editFormMode.value = item.path ?? item;
context.emit("command", item, rows);
if (item.path === "index") {
//list
await load(indexUrl);
await load();
} else if (item.path === "details") {
//details
const url = `${baseUrl}/${item.path}?${qs.stringify({ id: rows[0].id })}`;
editFormSchema.value = (await get(url)).data;
editFormModel.value = (await post(url)).data;
editFormTitle.value = `${querySchema.value?.title}${t("details")}`;
editFormTitle.value = `${config.edit.schema.title}${t("details")}`;
dialogVisible.value = true;
} else if (item.path === "create" || item.path === "update") {
//create
let url = `${baseUrl}/${item.path}`;
if (item.path === "update") {
url = `${url}?${qs.stringify({ id: rows[0].id })}`;
if (item.path === "create") {
editFormModel.value = schemaToModel(config.edit.schema);
} else {
const url = `${config.edit.updateUrl ?? config.query.url}/${rows[0].id}`;
editFormModel.value = (await request(url, null, { method: "get" })).data;
}
const vm = (await get(url)).data;
editFormSchema.value = vm.schema;
editFormModel.value = vm.model;
editFormTitle.value = `${t(item.path)}${querySchema.value?.title}`;
editFormTitle.value = `${t(item.path)}${config.edit.schema.title}`;
dialogVisible.value = true;
} else if (item.path === "delete") {
//delete
if (!rows.length) {
if (item.meta.isTop) {
// 批量删除
return;
} else {
// 单个删除
}
const url = `${baseUrl}/${item.path}`;
await post(
url,
rows.map((o) => o.id)
);
await load(indexUrl);
// await post(
// url,
// rows.map((o) => o.id)
// );
await load();
} else if (item.path === "export") {
//export
editFormTitle.value = `${t(item.path)}${querySchema.value?.title}`;
@ -505,12 +516,14 @@ export default {
const valid = await editFormRef.value.validate();
if (valid) {
editFormloading.value = true;
const url = `${baseUrl}/${editFormMode.value}`;
const result = await post(url, editFormModel.value);
const url =
(editFormMode.value === "create" ? config.edit.createUrl : config.edit.updateUrl) ?? config.query.url;
const method = editFormMode.value === "create" ? config.edit.createMethod : config.edit.updateMethod;
const result = await request(url, editFormModel.value, { method });
if (result.errors) {
model.errors = result.errors; //??
} else {
await load(indexUrl);
await load();
editFormMode.value = null;
dialogVisible.value = false;
}
@ -543,9 +556,9 @@ export default {
const response = await post(url, formData);
editFormloading.value = false;
dialogVisible.value = false;
await load(indexUrl);
await load();
} else if (editFormMode.value === "filter") {
await load(indexUrl);
await load();
dialogVisible.value = false;
}
};
@ -596,7 +609,19 @@ export default {
const handleChange = (uploadFile, uploadFiles) => {
fileList.value = uploadFiles;
};
const getButtonDisabled = async (src, row) => {
if (src) {
const method = await importFunction(src);
return src.startsWith("async") ? await method(row) : method(row);
}
return false;
};
onMounted(async () => {
for (const item of route.meta.children) {
if (item.meta.disabled?.constructor === String) {
item.meta.disabled = await importFunction(item.meta.disabled);
}
}
pushQueryList();
// if (!querySchema.value) {
// const vm = (await get(indexUrl)).data;
@ -609,17 +634,16 @@ export default {
// getSortModel(data.value);
// getColumns(vm.schema.properties.query);
// }
if (!config.value) {
if (!config) {
//
}
getColumns(config.value.table.schema);
queryModel.value = schemaToModel(config.value.query.schema);
getColumns(config.table.schema);
if (props.query) {
Object.assign(queryModel.value.query, props.query);
}
// getSortModel(data.value);
// getColumns(vm.schema.properties.query);
await load(indexUrl);
await load();
});
return {
config,
@ -664,6 +688,7 @@ export default {
pushQueryList,
fileList,
handleChange,
getButtonDisabled,
};
},
};

1
code/WebApp/vanilla/config/settings.js

@ -1,4 +1,5 @@
export default {
enableLocale: false,
baseURL: "http://dev.ccwin-in.com:10582/api",
//baseURL: "http://localhost:10130/api",
};

2
code/WebApp/vanilla/models/login.js

@ -20,7 +20,7 @@ export default function () {
password: {
title: "密码",
type: "string",
format: "password",
input: "password",
rules: [
{
required: true,

53
code/WebApp/vanilla/models/user.js

@ -5,6 +5,7 @@ const schema = {
userName: {
title: "用户名",
type: "string",
readOnly: true, //
showForList: true,
rules: [
{
@ -16,10 +17,14 @@ const schema = {
},
],
},
phoneNumber: {
title: "电话",
password: {
title: "密码",
type: "string",
input: "password",
},
name: {
title: "姓名",
type: "string",
showForList: true,
rules: [
{
@ -27,8 +32,8 @@ const schema = {
},
],
},
name: {
title: "姓名",
phoneNumber: {
title: "电话",
type: "string",
showForList: true,
rules: [
@ -52,26 +57,30 @@ const schema = {
type: "array",
input: "select",
multiple: true,
},
password: {
title: "密码",
type: "string",
input: "password",
rules: [
{
required: true,
message: "密码不能为空",
},
],
url: "identity/roles/all",
value: "name",
label: "name",
items: {
type: "string",
},
},
},
};
const url = "base/user";
const createUrl = url;
const updateUrl = url;
const deleteUrl = "api/identity/users";
const method = "get";
const createMethod = "post";
const updateMethod = "put";
const deleteMethod = "delete";
export default function () {
return {
query: {
url: "base/user",
method: "get",
url,
method,
schema: {
title: "用户",
type: "object",
@ -93,6 +102,16 @@ export default function () {
},
table: {
schema: schema,
selectable: (o) => o.name !== "admin",
},
edit: {
createUrl,
updateUrl,
deleteUrl,
createMethod,
updateMethod,
deleteMethod,
schema: schema,
},
};
}

23
code/WebApp/vanilla/request/index.js

@ -17,7 +17,7 @@ const addToken = async (options) => {
};
const getUrl = (url) => {
if (url.indexOf("/") === 0) {
if (url.startsWith("http")) {
return url;
}
let result = settings.baseURL;
@ -111,13 +111,28 @@ async function request(url, data, options, withoutToken = false) {
url = getUrl(url);
let defaultOptions = {
method: "POST",
headers: { "Accept-Language": "zh-Hans" },
headers: {
"Accept-Language": "zh-Hans",
"Content-Type": "application/json",
},
};
if (options) {
Object.assign(defaultOptions, options);
}
if (defaultOptions.method !== "GET" && !(data instanceof FormData)) {
defaultOptions.headers["Content-Type"] = "application/json";
if (defaultOptions.method === "GET" && data) {
url = `${url}?${qs.stringify(data)}`;
}
if (defaultOptions.method === "POST") {
if (data instanceof FormData) {
delete defaultOptions.headers["Content-Type"];
}
if (defaultOptions.headers["Content-Type"]?.startsWith("application/x-www-form-urlencoded")) {
defaultOptions.body = qs.stringify(data);
} else if (defaultOptions.headers["Content-Type"]?.startsWith("application/json")) {
defaultOptions.body = JSON.stringify(data);
} else {
defaultOptions.body = data;
}
}
if (!withoutToken) {
await addToken(defaultOptions);

3
code/WebApp/vanilla/router/routes.js

@ -52,6 +52,7 @@ export default [
title: "删除",
icon: "file",
permission: "AbpIdentity.Users.Delete",
isTop: true,
},
},
],
@ -83,6 +84,7 @@ export default [
icon: "file",
htmlClass: "el-button--primary",
permission: "AbpIdentity.Users.Update",
disabled: `(o) => o.isStatic`,
},
},
{
@ -92,6 +94,7 @@ export default [
title: "删除",
icon: "file",
permission: "AbpIdentity.Users.Delete",
disabled: `(o) => o.isStatic`,
},
},
],

25
code/WebApp/vanilla/utils/index.js

@ -120,5 +120,28 @@ function getFileName(contentDisposition) {
return decodeURIComponent(/filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i.exec(contentDisposition)[1]);
}
async function importModule(input) {
const dataUri = `data:text/javascript;charset=utf-8,${encodeURIComponent(input)}`;
const result = await import(dataUri /* @vite-ignore */);
return result.default;
}
// await importFunction('()=>console.log(123)');
async function importFunction(input) {
const src = input ?? `()=>{}`;
const result = await importModule(`export default ${src}`);
return result;
}
export default html;
export { persentFormat, bytesFormat, format, schemaToModel, listToTree, treeToList, getProp, getFileName };
export {
persentFormat,
bytesFormat,
format,
schemaToModel,
listToTree,
treeToList,
getProp,
getFileName,
importFunction,
};

113
code/WebApp/vanilla/utils/validation.js

@ -0,0 +1,113 @@
const messages = {
default: "%s验证失败",
required: "%s是必填项",
enum: "%s必须是%s之一",
whitespace: "%s不能为空",
// date: {
// format: '%s date %s is invalid for format %s',
// parse: '%s date could not be parsed, %s is invalid ',
// invalid: '%s date %s is invalid',
// },
types: {
string: "%s不是有效的字符串",
method: "%s不是有效的函数",
array: "%s不是有效的数组",
object: "%s不是有效的对象",
number: "%s不是有效的数字",
date: "%s不是有效的日期",
boolean: "%s不是有效的布尔值",
integer: "%s不是有效的整数",
float: "%s不是有效的浮点数",
regexp: "%s不是有效的正则表达式",
email: "%s不是有效的邮箱",
url: "%s不是有效的 url",
hex: "%s不是有效的十六进制",
},
string: {
len: "%s长度必须是%s",
min: "%s最小长度为%s",
max: "%s最大长度为%s",
range: "%s长度必须在%s和%s之间",
},
number: {
len: "%s必须等于%s",
min: "%s不小于%s",
max: "%s不大于%s",
range: "%s必须在%s和%s之间",
},
array: {
len: "%s的数量必须是%s",
min: "%s的数量不小于%s",
max: "%s的数量不大于%s",
range: "%s的数量必须在%s和%s之间",
},
pattern: {
mismatch: "%s的值 %s 不匹配模式 %s",
},
clone: function clone() {
const cloned = JSON.parse(JSON.stringify(this));
cloned.clone = this.clone;
return cloned;
},
//
compare: "%s 和 %s 输入必须一致",
true: "%s必须选中",
remote: "%s远程验证失败",
};
const validators = {
compare(rule, value, callback, source, options) {
const errors = [];
if (value && value !== rule.data[rule.compare]) {
const message = format(options.messages.compare, rule.title, rule.schema.properties[rule.compare].title);
errors.push(new Error(message));
}
callback(errors);
},
true(rule, value, callback, source, options) {
const errors = [];
if (!value) {
const message = format(options.messages.true, rule.title);
errors.push(new Error(message));
}
callback(errors);
},
remote(rule, value, callback, source, options) {
const errors = [];
const message = format(options.messages.remote, rule.title);
if (!value) {
callback(errors);
} else {
const config = {
url: rule.url,
method: rule.method ?? "get",
};
const data = { [rule.field]: value };
if (config.method === "get") {
config.params = data;
} else {
config.data = data;
}
request
.request(config)
.then((response) => {
if (response.status === 200) {
if (response.data.code) {
if (response.data.code !== 200) {
errors.push(new Error(1 + response.data.message));
}
}
} else {
errors.push(new Error(2 + response.data));
}
callback(errors);
})
.catch((o) => {
errors.push(o.response?.data?.message ?? message ?? o.message);
callback(errors);
});
}
},
};
export { messages };

15
code/WebApp/vanilla/views/login.js

@ -3,7 +3,7 @@ import { ref, reactive } from "vue";
import AppForm from "../components/form/index.js";
import { login, setRefreshToken, getUser, setAccessToken } from "../api/user.js";
import router, { refreshRouter } from "../router/index.js";
import { post } from "../request/index.js";
import request, { post } from "../request/index.js";
import LayoutLogo from "../layouts/logo.js";
import LayoutLocale from "../layouts/locale.js";
import LayoutFooter from "../layouts/footer.js";
@ -26,21 +26,16 @@ export default {
</div>
</el-main>
</el-container>`,
async setup() {
setup() {
const schema = reactive(useLoginModel());
const model = reactive(schemaToModel(schema));
const submit = async (callback, loading) => {
try {
const url = "connect-token";
const url = "base/token";
const appStore = useAppStore();
const result = await post(
url,
model,
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } },
true
);
const result = await request(url, model, { method: "POST" }, true);
if (!result.errors) {
appStore.token = result.data.access_token;
appStore.token = result.data.accessToken;
setAccessToken(appStore.token);
//setRefreshToken(result.data.refresh_token);
appStore.user = await getUser();

14
code/src/Modules/BaseService/BaseService.Application.Contracts/Systems/UserManagement/IUserAppService.cs

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using BaseService.BaseData.Permissions.Dto;
using BaseService.BaseData.Permissions.Dto;
using BaseService.RelationData.Dto;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Identity;
@ -17,7 +15,7 @@ namespace BaseService.Systems.UserManagement
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<IdentityUserDto> GetAsync(Guid id);
Task<IdentityUserUpdateDto> GetAsync(Guid id);
/// <summary>
/// 获取当前登录用户信息
@ -61,7 +59,6 @@ namespace BaseService.Systems.UserManagement
/// <returns></returns>
Task<ApplicationAuthes> GetAuthConfigAsync(Guid branchId);
/// <summary>
/// 重置当前登录用户的密码
/// </summary>
@ -70,6 +67,5 @@ namespace BaseService.Systems.UserManagement
/// <returns></returns>
//Task<IdentityUserDto> ResetPasswordCurrentUser(Guid id, IdentityUserCreateDto input);
Task<IdentityUserDto> ResetPasswordAsync(Guid id);
}
}
}

2
code/src/Modules/BaseService/BaseService.Application/BaseService.Application.csproj

@ -7,7 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IdentityModel" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="ValueInjecter" Version="3.2.0" />
<PackageReference Include="Volo.Abp.AutoMapper" Version="4.3.3" />
<PackageReference Include="Volo.Abp.Identity.Application" Version="4.3.3" />
<PackageReference Include="Volo.Abp.PermissionManagement.Application" Version="4.3.3" />

55
code/src/Modules/BaseService/BaseService.Application/UserManagement/TokenAppService.cs

@ -0,0 +1,55 @@
using IdentityModel.Client;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace BaseService.UserManagement
{
[Route("api/base/token")]
public class UserAppService : ApplicationService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
public UserAppService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
this._httpClientFactory = httpClientFactory;
this._configuration = configuration;
}
[HttpPost, AllowAnonymous, IgnoreAntiforgeryToken]
public async Task<TokenResponse> CreateAsync(LoginModel model)
{
var address = _configuration["AuthServer:Authority"];
var clientId = _configuration["AuthServer:ClientId"];
var clientSecret = _configuration["AuthServer:ClientSecret"];
var result = await _httpClientFactory.CreateClient().RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = $"{address.TrimEnd('/')}/connect/token",
GrantType = "password",
ClientId = clientId,
ClientSecret = clientSecret,
UserName = model.UserName,
Password = model.Password
}).ConfigureAwait(false);
return result;
}
public class LoginModel
{
[Display]
[Required]
public string UserName { get; set; }
[Display]
[Required]
public string Password { get; set; }
}
}
}

21
code/src/Modules/BaseService/BaseService.Application/UserManagement/UserAppService.cs

@ -1,16 +1,15 @@
using BaseService.BaseData;
using BaseService.Permissions;
using BaseService.BaseData.Permissions.Dto;
using BaseService.RelationBaseData;
using BaseService.RelationData.Dto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Omu.ValueInjecter;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BaseService.BaseData.Permissions.Dto;
using BaseService.RelationData.Dto;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
@ -38,6 +37,7 @@ namespace BaseService.Systems.UserManagement
//权限提供者类
private readonly IAbpAuthorizationPolicyProvider _abpAuthorizationPolicyProvider;
private readonly IAuthorizationService _authorizationService;
protected ICurrentUser CurrentUsers { get; }
@ -71,9 +71,11 @@ namespace BaseService.Systems.UserManagement
[HttpGet]
[Route("{id}")]
public async Task<IdentityUserDto> GetAsync(Guid id)
public async Task<IdentityUserUpdateDto> GetAsync(Guid id)
{
var dto = ObjectMapper.Map<IdentityUser, IdentityUserDto>(await UserManager.GetByIdAsync(id));
var user = await UserManager.GetByIdAsync(id);
var dto = Mapper.Map<IdentityUserUpdateDto>(user);
dto.RoleNames = (await UserRepository.GetRoleNamesAsync(id)).ToArray();
return dto;
}
@ -196,7 +198,7 @@ namespace BaseService.Systems.UserManagement
//获取用户的所有分支
var branchRoles = await GetUserBranchRolesAsync(CurrentUsers.GetId());
var groupBranchRoles = branchRoles.GroupBy(x => x.BranchId)
.Select(y => new {xx = new {BranchId = y.Key}, items = y});
.Select(y => new { xx = new { BranchId = y.Key }, items = y });
foreach (var group in groupBranchRoles
)
{
@ -225,7 +227,7 @@ namespace BaseService.Systems.UserManagement
//获取用户的所有分支
var branchRoles = await GetUserBranchRolesAsync(userId);
var groupBranchRoles = branchRoles.GroupBy(x => x.BranchId)
.Select(y => new {xx = new {BranchId = y.Key}, items = y});
.Select(y => new { xx = new { BranchId = y.Key }, items = y });
foreach (var group in groupBranchRoles)
{
var mybranchrole = new BranchRoleDto
@ -241,8 +243,6 @@ namespace BaseService.Systems.UserManagement
return new ListResultDto<BranchRoleDto>(branchList);
}
/// <summary>
/// 根据用户ID,获取当前登录用户的所有权限信息,带角色名称
/// </summary>
@ -373,7 +373,6 @@ namespace BaseService.Systems.UserManagement
return authConfig;
}
/// <summary>
/// 重置密码功能
/// </summary>

12
code/src/Modules/BaseService/BaseService.Host/.config/dotnet-tools.json

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "7.0.8",
"commands": [
"dotnet-ef"
]
}
}
}

72
code/src/Modules/BaseService/BaseService.Host/BaseServiceHostModule.cs

@ -1,39 +1,40 @@
using System.Linq;
using BaseService.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using Microsoft.OpenApi.Models;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Volo.Abp;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Auditing;
using Volo.Abp.Autofac;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.AspNetCore.MultiTenancy;
using System;
using Volo.Abp.TenantManagement;
using Volo.Abp.Threading;
using Volo.Abp.Data;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.PermissionManagement.HttpApi;
using Microsoft.AspNetCore.Cors;
using Volo.Abp.MultiTenancy;
using BaseService.EntityFrameworkCore;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.PermissionManagement.HttpApi;
using Volo.Abp.Security.Claims;
using System.Security.Claims;
using Volo.Abp.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.TenantManagement;
using Volo.Abp.Threading;
//using Win.Sfs.SettleAccount;
//using Win.Sfs.BaseData;
//using BaseData;
namespace BaseService
{
[DependsOn(
@ -45,8 +46,8 @@ namespace BaseService
typeof(AbpPermissionManagementHttpApiModule),
typeof(AbpTenantManagementHttpApiModule),
typeof(AbpIdentityHttpApiModule),
// typeof(BaseDataHttpApiModule),
//typeof(BaseDataApplicationContractsModule),
// typeof(BaseDataHttpApiModule),
//typeof(BaseDataApplicationContractsModule),
//typeof(SettleAccountHttpApiModule),
typeof(AbpAspNetCoreSerilogModule)
)]
@ -56,12 +57,15 @@ namespace BaseService
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHttpClient();
Configure<AbpAntiForgeryOptions>(O => O.AutoValidate = false);
var configuration = context.Services.GetConfiguration();
ConfigureConventionalControllers();
ConfigureMultiTenancy();
ConfigureJwt(context, configuration);
ConfigureSwagger(context);
@ -73,7 +77,7 @@ namespace BaseService
ConfigureAuditing();
ConfigureCros(context, configuration);
ConfigureLocalization();
ConfigurePasswordSet(context);
@ -87,6 +91,7 @@ namespace BaseService
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
});
}
/// <summary>
/// 设置密码强度
/// </summary>
@ -148,7 +153,6 @@ namespace BaseService
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
context.Services.AddDataProtection()
.PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
}
private void ConfigureDbContext()
@ -160,15 +164,15 @@ namespace BaseService
{
context.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo {Title = "BaseService Service API", Version = "v1"});
options.SwaggerDoc("v1", new OpenApiInfo { Title = "BaseService Service API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "请输入JWT令牌,例如:Bearer 12345abcdef",
Description = "请输入 JWT Token",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Type = SecuritySchemeType.Http,
Scheme = "Bearer"
});
@ -177,16 +181,9 @@ namespace BaseService
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "Bearer"}
},
new List<string>()
new string[] { }
}
});
});
@ -224,6 +221,7 @@ namespace BaseService
;
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
@ -273,4 +271,4 @@ namespace BaseService
});
}
}
}
}

20
code/src/Modules/BaseService/BaseService.Host/appsettings.json

@ -1,11 +1,14 @@
{
"AuthServer": {
"Authority": "http://dev.ccwin-in.com:10580"
"Authority": "http://dev.ccwin-in.com:10580",
"ClientId": "basic-web",
"ClientSecret": ""
},
"App": {
"CorsOrigins": "http://localhost:9527,http://dev.ccwin-in.com:10588,http://localhost:44307"
},
"ConnectionStrings": {
//"Default": "Server=localhost;Database=BJABP;User ID=sa;Password=aA123456!;Trusted_Connection=False;TrustServerCertificate=True",
"Default": "Server=dev.ccwin-in.com,13319;Database=BJABP;User ID=ccwin-in;Password=Microsoft@2022;Trusted_Connection=False;TrustServerCertificate=True"
},
"ElasticSearch": {
@ -19,22 +22,7 @@
"Default": "Warning"
}
},
<<<<<<< HEAD
//"Settings": {
// "Abp.Localization.DefaultLanguage": "zh-Hans",
// "Abp.Identity.Password.RequireNonAlphanumeric": "false", //ȥĸ
// "Abp.Identity.Password.RequireUppercase": "false", //ȥд
// "Abp.Identity.Password.RequireLowercase": "false", //ȥСд
// "Abp.Identity.Password.RequiredLength": "1",
// "Abp.Identity.SignIn.RequireConfirmedEmail": "true",
// //û
// "Abp.Identity.Lockout.AllowedForNewUsers": "true",
// "Abp.Identity.Lockout.MaxFailedAccessAttempts": "3"
//},
"AllowedHosts": "*"
=======
"AllowedHosts": "*",
"RePassword": "111111"
>>>>>>> 1c2946500765850db29fa7d216f5e55e2e4de888
}

BIN
docs/20230711-151906.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

1021
docs/SqlSchemaCompare2.scmp

File diff suppressed because it is too large
Loading…
Cancel
Save