6. 用户管理
6.1. 添加数据入口
在首页 app.tsx
中添加用户管理的Servlet,举例:users.tier
this.anReact = new AnReactExt(this.anClient, this.errorCtx)
.extendPorts({
menu: "menu.serv",
userstier: "users.tier",
gpatier: "gpa.tier",
mykidstier: "mykids.tier"
});
在后端创建 Servlet 类,举例:UsersTier.java
@WebServlet(description = "Semantic tier: users", urlPatterns = { "/users.tier" })
public class UsersTier extends ServPort<UserstReq> {
private static final long serialVersionUID = 1L;
protected static DATranscxt st;
public static String mtabl = "a_users";
static {
try {
st = new DATranscxt(null);
} catch (SemanticException | SQLException | SAXException | IOException e) {
e.printStackTrace();
}
}
public UsersTier() {
super(Port.userstier);
}
......
注意
新框架的数据处理都放到后端了,前端只需把参数传给后端,后端处理后把结果返给前端
6.2. 定义查询组件
在用户管理组件中定义查询组件,需要继承 CrudCompW 类,举例:
class UsersQuery extends CrudCompW<Comprops & {pageInf: PageInf, onQuery: (conds: PageInf) => void}> {
conds = [
{ name: 'userName', field: 'userName', type: 'text', val: undefined, label: L('Student'),
grid: {sm: 3, md: 2} } as AnlistColAttrs<any, any>,
{ name: 'orgId', field: 'orgId', type: 'cbb', val: undefined, label: L('Class'),
sk: Protocol.sk.cbbOrg, nv: {n: 'text', v: 'value'}, grid: {sm: 3, md: 2} } as ComboCondType,
// { name: 'roleId', field: 'roleId', type: 'cbb', val: undefined, label: L('Role'),
// sk: Protocol.sk.cbbRole, nv: {n: 'text', v: 'value'}, grid: {md: 2, sm: 3} },
];
注意
conds 里面配置的就是查询条件,其中field表示字段名,其中label查询框内文本, type表示字段类型(cbb表示下拉框,如果配置了sk,会自动填充下拉框数据)
6.3. 定义查询方法
① 在前端组件中定义查询列表的方法 toSearch(),举例:
toSearch(_condts: PageInf): void {
if (!this.tier) {
console.warn("really happens?")
return;
}
let that = this;
this.tier.records( this.q,
(_cols, _rows) => {
that.state.selected.ids.clear();
that.setState( {buttons: {add: true, edit: false, del: false}} );
} );
}
② 在前端 UsersTier 类中定义查询列表方法 records(),举例:
records(conds: PageInf, onLoad: OnLoadOk<Tierec>) {
if (!this.client) return;
let client = this.client;
let that = this;
let req = client.userReq(this.uri, this.port,
new UserstReq( this.uri, conds )
.A(UserstReq.A.records) );
client.commit(req,
(resp) => {
let {cols, rows} = AnsonResp.rs2arr(resp.Body().Rs());
that.rows = rows;
conds.total = resp.Body()?.Rs()?.total || 0;
onLoad(cols, rows as Tierec[]);
},
this.errCtx);
}
③ 在后端 UsersTier.java 中定义查询列表方法 records(),举例:
protected AnsonResp records(UserstReq jreq, IUser usr)
throws SemanticException, TransException, SQLException {
Query q = st.select(mtabl, "u")
.page(jreq.page)
.col("userId").col("userName").col("orgName").col("roleName")
.l("a_orgs", "o", "o.orgId = u.orgId")
.l("a_roles", "r", "r.roleId = u.roleId");
if (!LangExt.isEmpty(jreq.userName))
q.whereLike("userName", jreq.userName);
if (!LangExt.isEmpty(jreq.userId))
q.whereEq("userId", jreq.userId);
if (!LangExt.isEmpty(jreq.roleId))
q.whereEq("u.roleId", jreq.roleId);
if (!LangExt.isEmpty(jreq.orgId))
q.whereEq("u.orgId", jreq.orgId);
AnResultset rs = (AnResultset) q
.rs(st.instancontxt(Connects.uri2conn(jreq.uri()), usr))
.rs(0);
return new AnsonResp().rs(rs);
}
注意
前端查询框内的值,后端可以直接调用 jreq.xxx 获取(xxx一般为前端conds里配置的name)
6.4. 关联前后端
前后端能关联的必须条件:前端定义的 UserstReq.A
与后端定义的 UserstReq.A
要对应起来!
static A = {
records: 'records',
rec: 'rec',
update: 'u',
insert: 'c',
del: 'd',
}
public static class A {
/** Ask for loading records */
public static final String records = "records";
/** Ask for loading a rec */
public static final String rec = "rec";
public static final String update = "u";
public static final String avatar = "u/avatar";
public static final String insert = "c";
public static final String del = "d";
}
注意
通用方法的名称约定如下,其余方法名可自定义:
records 表示查询列表(带分页的,前端传page和size即可)
rec 查询单条记录
update 修改单条记录
insert 新增单条记录
del 删除单条或多条记录
6.5. 调用表格组件
直接调用封装好的表格组件 AnTablPager,举例:
<AnTablPager
pk={tier.pkval.pk}
className={classes.root}
checkbox={tier.checkbox}
selected={this.state.selected}
columns={tier.columns()}
rows={tier.rows}
sizeOptions={[5, 8, 10]}
pageInf={this.q}
onPageChange={this.onPageInf}
onSelectChange={this.onTableSelect}
/>
注意
rows为查询组件的返回结果,sizeOptions为每页行数,pageInf为分页信息
6.6. 定义表格的列
表格中的列需要在 UsersTier
中单独定义,举例:
_cols = [
{ label: L('check'), field: 'userId', checkbox: true },
{ label: L('Log Id'), field: 'userId' },
{ label: L('User Name'), field: 'userName' },
{ label: L('Organization'), field: 'orgName',
sk: Protocol.sk.cbbOrg, nv: {n: 'text', v: 'value'} },
{ label: L('Role'), field: 'roleName',
sk: Protocol.sk.cbbRole, nv: {n: 'text', v: 'value'} }
] as Array<AnlistColAttrs<JSX.Element, CompOpts>>;
注意
label为表头的名称,field为数据库字段
6.7. 定义form表单
在 UsersTier
配置好字段后,在详情页面调用 TRecordForm 组件,即可自动生成表单:
_fields = [
{ type: 'text', field: 'userId', label: L('Log ID'),
validator: {len: 20, notNull: true} },
{ type: 'text', field: 'userName', label: L('User Name') },
{ type: 'password', field: 'pswd', label: L('Password'),
validator: {notNull: true} },
{ type: 'cbb', field: 'roleId', label: L('Role'),
grid: {md: 5},
sk: Protocol.sk.cbbRole, nv: {n: 'text', v: 'value'},
validator: {notNull: true} } as TierComboField<JSX.Element, CompOpts>,
{ type: 'cbb', field: 'orgId', label: L('Organization'),
grid: {md: 5},
sk: Protocol.sk.cbbOrg, nv: {n: 'text', v: 'value'},
validator: {notNull: true} } as TierComboField<JSX.Element, CompOpts>,
] as AnlistColAttrs<JSX.Element, CompOpts>[];
注意
type为text表示文本框,password为密码框,cbb为下拉框(配置了sk,会自动填充下拉数据)
6.8. 提交form表单
①先在 UsersTier
定义保存方法(新增和修改共用),举例:
saveRec(opts: { uri: string; crud: CRUD; pkval: string; }, onOk: OnCommitOk) {
if (!this.client) return;
let client = this.client;
let that = this;
let { uri, crud } = opts;
if (crud === CRUD.u && !this.pkval)
throw Error("Can't update with null ID.");
this.rec.iv = null; // working - but why?
let req = this.client.userReq(uri, this.port,
new UserstReq( uri, { record:this.rec, relations: this.collectRelations(), pk: this.pkval.v})
.A(crud === CRUD.c ? UserstReq.A.insert : UserstReq.A.update) );
client.commit(req,
(resp) => {
let bd = resp.Body();
if (crud === CRUD.c)
// NOTE:
// resulving auto-k is a typicall semantic processing, don't expose this to caller
that.pkval.v = bd.resulve(that.pkval.tabl, that.pkval.pk, that.rec);
onOk(resp);
},
this.errCtx);
}
②后端在 UsersTier.java
类定义新增方法,举例:
protected AnsonResp ins(UserstReq jreq, IUser usr)
throws SemanticException, TransException, SQLException {
if (jreq.record == null)
throw new SemanticException("Inserting a null record ...");
ISemantext stx = st.instancontxt(Connects.uri2conn(jreq.uri()), usr);
AnResultset rs = (AnResultset) st.select(mtabl, "u")
.col(Funcall.count("userId"), "c")
.whereEq("userId", jreq.record.get("userId"))
.rs(stx)
.rs(0);
rs.beforeFirst().next();
if (rs.getInt("c") > 0)
throw new SemanticException("record id doesn't exist");
SemanticObject res = (SemanticObject)
((Insert) jreq.nvs(st.insert(mtabl, usr)))
.ins(stx);
return new AnsonResp().data(res.props());
}
③前后端关联新增方法:
static A = {
records: 'records',
rec: 'rec',
update: 'u',
insert: 'c',
del: 'd',
}
public static class A {
/** Ask for loading records */
public static final String records = "records";
/** Ask for loading a rec */
public static final String rec = "rec";
public static final String update = "u";
public static final String avatar = "u/avatar";
public static final String insert = "c";
public static final String del = "d";
}
参见