mirror of
https://gitee.com/dotnetchina/OpenAuth.Net.git
synced 2025-07-16 07:59:54 +08:00
可以编辑表单
This commit is contained in:
parent
1d87bc4262
commit
25ab9739a0
@ -36,7 +36,7 @@ namespace OpenAuth.App
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GridData Load(int pageCurrent, int pageSize)
|
public GridData Load(int pageCurrent, int pageSize)
|
||||||
{
|
{
|
||||||
var result = new GridData
|
var result = new GridData
|
||||||
{
|
{
|
||||||
page = pageCurrent
|
page = pageCurrent
|
||||||
@ -48,5 +48,19 @@ namespace OpenAuth.App
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SaveForm(string keyValue, WFFrmMain entity)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(keyValue))
|
||||||
|
{
|
||||||
|
_unitWork.Add(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_unitWork.Update(u =>u.Id, entity);
|
||||||
|
}
|
||||||
|
_unitWork.Save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ using System.Web.Mvc;
|
|||||||
using Infrastructure;
|
using Infrastructure;
|
||||||
using LeaRun.Util.WebControl;
|
using LeaRun.Util.WebControl;
|
||||||
using OpenAuth.App;
|
using OpenAuth.App;
|
||||||
|
using OpenAuth.App.SSO;
|
||||||
|
using OpenAuth.Domain;
|
||||||
using OpenAuth.Domain.Service;
|
using OpenAuth.Domain.Service;
|
||||||
using OpenAuth.Mvc.Controllers;
|
using OpenAuth.Mvc.Controllers;
|
||||||
|
|
||||||
@ -134,14 +136,23 @@ namespace OpenAuth.Mvc.Areas.FlowManage.Controllers
|
|||||||
///// <param name="keyValue">主键值</param>
|
///// <param name="keyValue">主键值</param>
|
||||||
///// <param name="userEntity">用户实体</param>
|
///// <param name="userEntity">用户实体</param>
|
||||||
///// <returns></returns>
|
///// <returns></returns>
|
||||||
//[HttpPost]
|
[HttpPost]
|
||||||
//[ValidateAntiForgeryToken]
|
public string SaveForm(string keyValue, WFFrmMain userEntity)
|
||||||
//[AjaxOnly]
|
{
|
||||||
//public ActionResult SaveForm(string keyValue, WFFrmMainEntity userEntity)
|
try
|
||||||
//{
|
{
|
||||||
// wfFrmMainBLL.SaveForm(keyValue, userEntity);
|
var user = AuthUtil.GetCurrentUser();
|
||||||
// return Success("操作成功。");
|
userEntity.ModifyUserId = user.User.Account;
|
||||||
//}
|
userEntity.ModifyUserName = user.User.Name;
|
||||||
|
_wfFrmMainBll.SaveForm(keyValue, userEntity);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Result.Status = false;
|
||||||
|
Result.Message = e.Message;
|
||||||
|
}
|
||||||
|
return Result.ToJson();
|
||||||
|
}
|
||||||
///// <summary>
|
///// <summary>
|
||||||
///// (启用、禁用)
|
///// (启用、禁用)
|
||||||
///// </summary>
|
///// </summary>
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
@{
|
||||||
|
ViewBag.Title = "表单预览";
|
||||||
|
Layout = "~/Views/Shared/_FlowForm.cshtml";
|
||||||
|
}
|
||||||
|
<script>
|
||||||
|
var keyValue = request('keyValue');
|
||||||
|
$(function () {
|
||||||
|
$('#formAreas').css("min-height", $(window).height() - 82);
|
||||||
|
$('#formAreas').css("width", $(window).width() * 0.9);
|
||||||
|
$('#previewpage').css("height", $(window).height()-20);
|
||||||
|
$.SetForm({
|
||||||
|
url: "../../FlowManage/FormDesign/GetFormJson",
|
||||||
|
param: { keyValue: keyValue },
|
||||||
|
success: function (data) {
|
||||||
|
$('#frmpreview').frmPreview({
|
||||||
|
tablecotent: data.FrmContent
|
||||||
|
});
|
||||||
|
$('#frmname').html(data.FrmName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//resize重设(表格、树形)宽高
|
||||||
|
$(window).resize(function (e) {
|
||||||
|
window.setTimeout(function () {
|
||||||
|
$('#previewpage').css("height", e.currentTarget.innerHeight-20);
|
||||||
|
$('#formAreas').css("min-height", e.currentTarget.innerHeight - 82).css("width", e.currentTarget.innerWidth * 0.9);
|
||||||
|
}, 200);
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<div id="previewpage" style="overflow-y:auto;background-color:#fff;margin:10px;border:1px solid #ccc;">
|
||||||
|
<div id="formAreas" style="margin: 30px auto;max-width: 1000px;">
|
||||||
|
<div style="border-bottom:1px solid #ccc;text-align:center;"><span id="frmname"></span></div>
|
||||||
|
<div class="app_layout app_preview" id="frmpreview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
259
OpenAuth.Mvc/Areas/FlowManage/Views/FormDesign/FrmBuider.cshtml
Normal file
259
OpenAuth.Mvc/Areas/FlowManage/Views/FormDesign/FrmBuider.cshtml
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
@{
|
||||||
|
ViewBag.Title = "表单创建";
|
||||||
|
Layout = "~/Views/Shared/_FlowForm.cshtml";
|
||||||
|
}
|
||||||
|
<script>
|
||||||
|
var keyValue = request('keyValue');
|
||||||
|
var postData = {};//提交数据
|
||||||
|
var frmdatabase = "";
|
||||||
|
var frmapp = "";
|
||||||
|
$(function () {
|
||||||
|
initialPage();
|
||||||
|
})
|
||||||
|
//初始化页面
|
||||||
|
function initialPage() {
|
||||||
|
$('#step-1 .panel-body').height($(window).height() - 228);
|
||||||
|
$('#Description').height($(window).height() - 380);
|
||||||
|
$('#step-2 .tab-content').height($(window).height() - 87);
|
||||||
|
initBaseInfo();
|
||||||
|
//加载导向
|
||||||
|
$('#wizard').wizard().on('change', function (e, data) {
|
||||||
|
var $finish = $("#btn_finish");
|
||||||
|
var $next = $("#btn_next");
|
||||||
|
if (data.direction == "next") {
|
||||||
|
switch (data.step) {
|
||||||
|
case 1:
|
||||||
|
return bindingBase();
|
||||||
|
break;
|
||||||
|
case 2://绑定表单
|
||||||
|
if (!bindingFrm()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$finish.removeAttr('disabled');
|
||||||
|
$next.attr('disabled', 'disabled');
|
||||||
|
$('#btn_caogao').attr('disabled', 'disabled');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$finish.attr('disabled', 'disabled');
|
||||||
|
$next.removeAttr('disabled');
|
||||||
|
$('#btn_caogao').removeAttr('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('input[name="isSystemTable"]').click(function () {
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value == 0) {
|
||||||
|
$('.Systemtable').hide();
|
||||||
|
$('.Systemtable').find('.ui-select').removeAttr('isvalid');
|
||||||
|
$('.Systemtable').find('.ui-select').removeAttr('checkexpession');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('.Systemtable').show();
|
||||||
|
$('.Systemtable').find('.ui-select').attr('isvalid', 'yes');
|
||||||
|
$('.Systemtable').find('.ui-select').attr('checkexpession', 'NotNull');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//获取表单
|
||||||
|
if (!!keyValue) {
|
||||||
|
//获取表单
|
||||||
|
$.SetForm({
|
||||||
|
url: "../../FlowManage/FormDesign/GetFormJson",
|
||||||
|
param: { keyValue: keyValue },
|
||||||
|
success: function (data) {
|
||||||
|
$("#step-1").SetWebControls(data);
|
||||||
|
$('#isSystemTable' + data.isSystemTable).trigger('click');
|
||||||
|
if (data.isSystemTable == 1)
|
||||||
|
{
|
||||||
|
$("#step-1").find("#FrmDbId").trigger("change");
|
||||||
|
$("#step-1").find("#FrmTable").ComboBoxSetValue(data.FrmTable);
|
||||||
|
$("#step-1").find("#FrmTable").trigger("change");
|
||||||
|
$("#step-1").find("#FrmTableId").ComboBoxSetValue(data.FrmTableId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrmInfo(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//保存为草稿
|
||||||
|
$('#btn_caogao').click(function () {
|
||||||
|
if (!$('#step-1').Validform()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var _data = $("#step-1").GetWebControls(keyValue);
|
||||||
|
postData = $.extend(postData, _data);
|
||||||
|
postData["EnabledMark"] = 3;
|
||||||
|
var frmcotentls = frmapp.getData(true);
|
||||||
|
postData.FrmContent = JSON.stringify(frmcotentls);
|
||||||
|
$.SaveForm({
|
||||||
|
url: "../../FlowManage/FormDesign/SaveForm?keyValue=" + keyValue,
|
||||||
|
param: postData,
|
||||||
|
loading: "正在保存数据...",
|
||||||
|
success: function () {
|
||||||
|
$.currentIframe().$("#gridTable").trigger("reloadGrid");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*=========基本配置(begin)==================================================================*/
|
||||||
|
function initBaseInfo()
|
||||||
|
{
|
||||||
|
//性别
|
||||||
|
$("#Gender").ComboBox({
|
||||||
|
description: "==请选择==",
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
function bindingBase()
|
||||||
|
{
|
||||||
|
if (!$('#step-1').Validform()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var _postData = $("#step-1").GetWebControls(keyValue);
|
||||||
|
postData.isSystemTable = $('input[name="isSystemTable"]:checked').val();
|
||||||
|
postData = $.extend(postData, _postData);
|
||||||
|
setFrmInfo(postData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/*=========基本配置(end)====================================================================*/
|
||||||
|
|
||||||
|
/*=========表单选择(begin)==================================================================*/
|
||||||
|
function setFrmInfo(data)
|
||||||
|
{
|
||||||
|
var _height = $(window).height() - 87 < 410 ? 410 : $(window).height() - 87;
|
||||||
|
postData.FrmContent = data.FrmContent;
|
||||||
|
if (data.isSystemTable == "0") {
|
||||||
|
frmapp = $('#frmdesign').frmDesign({
|
||||||
|
Height: _height,
|
||||||
|
frmContent: postData.FrmContent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var _frmdatabase = [];
|
||||||
|
for (var i in frmdatabase)
|
||||||
|
{
|
||||||
|
if (frmdatabase[i].column != postData.FrmTableId)
|
||||||
|
{
|
||||||
|
_frmdatabase.push(frmdatabase[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frmapp = $('#frmdesign').frmDesign({
|
||||||
|
Height: _height,
|
||||||
|
tablefiledJsonData: _frmdatabase,
|
||||||
|
isSystemTable: postData.isSystemTable,
|
||||||
|
frmContent: postData.FrmContent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function bindingFrm() {
|
||||||
|
var frmcotentls = frmapp.getData();
|
||||||
|
if (!frmcotentls) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
postData.FrmContent = JSON.stringify(frmcotentls);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/*=========表单选择(end)====================================================================*/
|
||||||
|
|
||||||
|
/*=========创建完成(begin)==================================================================*/
|
||||||
|
function finishbtn() {
|
||||||
|
postData["EnabledMark"] = 1;
|
||||||
|
$.SaveForm({
|
||||||
|
url: "../../FlowManage/FormDesign/SaveForm?keyValue=" + keyValue,
|
||||||
|
param: postData,
|
||||||
|
loading: "正在保存数据...",
|
||||||
|
success: function () {
|
||||||
|
$.currentIframe().$("#gridTable").trigger("reloadGrid");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/*=========创建完成(end)====================================================================*/
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="widget-body">
|
||||||
|
<div id="wizard" class="wizard" data-target="#wizard-steps" >
|
||||||
|
<ul class="steps">
|
||||||
|
<li data-target="#step-1" class="active"><span class="step">1</span>基本配置<span class="chevron"></span></li>
|
||||||
|
<li data-target="#step-2"><span class="step">2</span>表单设计<span class="chevron"></span></li>
|
||||||
|
<li data-target="#step-4"><span class="step">3</span>创建完成<span class="chevron"></span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="step-content wizard-step-content" id="wizard-steps">
|
||||||
|
<div class="step-pane wizard-step-pane active" id="step-1">
|
||||||
|
<div class="alert alert-danger" style="text-align: left; margin-bottom: 10px;">
|
||||||
|
<i class="fa fa-warning alert-dismissible" style="position: relative; top: 1px; font-size: 15px; padding-right: 5px;"></i>
|
||||||
|
请你填写表单信息,用于创建或修改表单!
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">表单基本信息配置</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<table class="form">
|
||||||
|
<tr>
|
||||||
|
<td class="formTitle">表单编号<font face="宋体">*</font></td>
|
||||||
|
<td class="formValue">
|
||||||
|
<input id="FrmCode" type="text" class="form-control" placeholder="请输入编号" isvalid="yes" checkexpession="NotNull" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="formTitle">表单名称<font face="宋体">*</font></td>
|
||||||
|
<td class="formValue">
|
||||||
|
<input id="FrmName" type="text" class="form-control" placeholder="请输入名称" isvalid="yes" checkexpession="NotNull" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="formTitle">是否建表<font face="宋体">*</font></td>
|
||||||
|
<td class="formValue">
|
||||||
|
<div class="rdio rdio-color_a"><input type="radio" name="isSystemTable" id="isSystemTable0" value="0" checked /><label for="isSystemTable0">否</label></div>
|
||||||
|
<div class="rdio rdio-color_c"><input type="radio" name="isSystemTable" id="isSystemTable1" value="1" /><label for="isSystemTable1">是</label></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="Systemtable" style="display:none">
|
||||||
|
<td class="formTitle">数据库<font face="宋体">*</font></td>
|
||||||
|
<td class="formValue">
|
||||||
|
<div id="FrmDbId" type="selectTree" class="ui-select"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="Systemtable" style="display:none">
|
||||||
|
<td class="formTitle">数据表<font face="宋体">*</font></td>
|
||||||
|
<td class="formValue">
|
||||||
|
<div id="FrmTable" type="select" class="ui-select"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="Systemtable" style="display:none">
|
||||||
|
<td class="formTitle">绑定主键<font face="宋体">*</font></td>
|
||||||
|
<td class="formValue">
|
||||||
|
<div id="FrmTableId" type="select" class="ui-select"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="formTitle formTitle-top">
|
||||||
|
备注
|
||||||
|
</th>
|
||||||
|
<td class="formValue">
|
||||||
|
<textarea id="Description" class="form-control" style="height: 180px;"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-pane flowapp" id="step-2">
|
||||||
|
<div id="frmdesign"></div>
|
||||||
|
</div>
|
||||||
|
<div class="step-pane" id="step-4">
|
||||||
|
<div class="drag-tip">
|
||||||
|
<i class="fa fa-check-circle"></i>
|
||||||
|
<p >设计完成,请点击保存</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-button" id="wizard-actions">
|
||||||
|
<a id="btn_last" disabled class="btn btn-default btn-prev">上一步</a>
|
||||||
|
<a id="btn_next" class="btn btn-default btn-next">下一步</a>
|
||||||
|
<a id="btn_caogao" class="btn btn-info">保存草稿</a>
|
||||||
|
<a id="btn_finish" disabled class="btn btn-success" onclick="finishbtn();">保存</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -79,7 +79,7 @@ function edit() {
|
|||||||
skin: 'layui-layer-rim', //加上边框
|
skin: 'layui-layer-rim', //加上边框
|
||||||
area: ['1200px', '700px'], //宽高
|
area: ['1200px', '700px'], //宽高
|
||||||
maxmin: true, //开启最大化最小化按钮
|
maxmin: true, //开启最大化最小化按钮
|
||||||
content: '/FlowManage/FormDesign/FlowFrmBuider?keyValue=' + selected.Id
|
content: '/FlowManage/FormDesign/FrmBuider?keyValue=' + selected.Id
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ function add() {
|
|||||||
skin: 'layui-layer-rim', //加上边框
|
skin: 'layui-layer-rim', //加上边框
|
||||||
area: ['1200px', '700px'], //宽高
|
area: ['1200px', '700px'], //宽高
|
||||||
maxmin: true, //开启最大化最小化按钮
|
maxmin: true, //开启最大化最小化按钮
|
||||||
content: '/FlowManage/FormDesign/FlowFrmBuider',
|
content: '/FlowManage/FormDesign/FrmBuider',
|
||||||
end: function() {
|
end: function() {
|
||||||
list.reload();
|
list.reload();
|
||||||
}
|
}
|
||||||
|
@ -1169,6 +1169,8 @@
|
|||||||
<Content Include="Areas\FlowManage\Views\FlowDesign\PreviewIndex.cshtml" />
|
<Content Include="Areas\FlowManage\Views\FlowDesign\PreviewIndex.cshtml" />
|
||||||
<Content Include="Areas\FlowManage\Views\FlowInstances\VerificationForm.cshtml" />
|
<Content Include="Areas\FlowManage\Views\FlowInstances\VerificationForm.cshtml" />
|
||||||
<Content Include="Areas\FlowManage\Views\FormDesign\Index.cshtml" />
|
<Content Include="Areas\FlowManage\Views\FormDesign\Index.cshtml" />
|
||||||
|
<Content Include="Areas\FlowManage\Views\FormDesign\FormPreview.cshtml" />
|
||||||
|
<Content Include="Areas\FlowManage\Views\FormDesign\FrmBuider.cshtml" />
|
||||||
<None Include="Properties\PublishProfiles\default.pubxml" />
|
<None Include="Properties\PublishProfiles\default.pubxml" />
|
||||||
<None Include="Views\Error\NoAccess.cshtml" />
|
<None Include="Views\Error\NoAccess.cshtml" />
|
||||||
<Content Include="Views\Home\git.cshtml" />
|
<Content Include="Views\Home\git.cshtml" />
|
||||||
|
@ -32,8 +32,7 @@
|
|||||||
<link href="/Content/myace.css" rel="stylesheet"/>
|
<link href="/Content/myace.css" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/Content/plugins/zTree/css/metroStyle/metroStyle.css" rel="stylesheet"/>
|
<link href="/Content/plugins/zTree/css/metroStyle/metroStyle.css" rel="stylesheet"/>
|
||||||
<link href="~/Content/layui/css/modules/layer/layer.css" rel="stylesheet" />
|
<link href="/Content/layui/css/modules/layer/layer.css" rel="stylesheet" />
|
||||||
<link href="~/Content/plugins/layer/skin/layer.ext.css" rel="stylesheet" />
|
|
||||||
<!--[if lte IE 9]>
|
<!--[if lte IE 9]>
|
||||||
<link rel="stylesheet" href="/Content/ace/css/ace-ie.css" />
|
<link rel="stylesheet" href="/Content/ace/css/ace-ie.css" />
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@ -45,8 +44,8 @@
|
|||||||
<script src="~/Content/plugins/jqGrid/jquery.jqGrid.src.js"></script>
|
<script src="~/Content/plugins/jqGrid/jquery.jqGrid.src.js"></script>
|
||||||
<script src="~/Content/plugins/jqGrid/i18n/grid.locale-en.js"></script>
|
<script src="~/Content/plugins/jqGrid/i18n/grid.locale-en.js"></script>
|
||||||
<script src="~/Content/plugins/zTree/js/jquery.ztree.core.js"></script>
|
<script src="~/Content/plugins/zTree/js/jquery.ztree.core.js"></script>
|
||||||
<script src="~/Content/layui/layui.js"></script>
|
<script src="/Content/layui/layui.js"></script>
|
||||||
<script src="~/Content/layui/lay/modules/layer.js"></script>
|
<script src="/Content/layui/lay/modules/layer.js"></script>
|
||||||
<script src="~/Content/vue.js"></script>
|
<script src="~/Content/vue.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -53,7 +53,6 @@ namespace OpenAuth.Repository.Models.Mapping
|
|||||||
.IsOptional();
|
.IsOptional();
|
||||||
Property(t => t.FrmContent)
|
Property(t => t.FrmContent)
|
||||||
.HasColumnName("FrmContent")
|
.HasColumnName("FrmContent")
|
||||||
.HasMaxLength(16)
|
|
||||||
.IsOptional();
|
.IsOptional();
|
||||||
Property(t => t.SortCode)
|
Property(t => t.SortCode)
|
||||||
.HasColumnName("SortCode")
|
.HasColumnName("SortCode")
|
||||||
|
Loading…
Reference in New Issue
Block a user