优化分销
This commit is contained in:
@@ -166,7 +166,7 @@ export function shopRechargeGet() {
|
|||||||
// 获取店铺用户列表
|
// 获取店铺用户列表
|
||||||
export function getShopUserList(params) {
|
export function getShopUserList(params) {
|
||||||
return request({
|
return request({
|
||||||
url: `${Account_BaseUrl + "/admin/shopUser/getPage"}`,
|
url: `${Account_BaseUrl + "/admin/shopUser"}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params
|
params
|
||||||
});
|
});
|
||||||
@@ -738,6 +738,23 @@ export function distributionShopFlow(params) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分销员:获取邀请人分页列表
|
||||||
|
export function distributionUserInviteUser(params) {
|
||||||
|
return request({
|
||||||
|
url: `${Market_BaseUrl}/admin/distribution/user/inviteUser`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分销员:重置分销员等级
|
||||||
|
export function distributionUserResetLevel(data) {
|
||||||
|
return request({
|
||||||
|
url: `${Market_BaseUrl}/admin/distribution/user/resetLevel`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,13 @@
|
|||||||
<el-tag disable-transitions type="warning" v-if="scope.row.sex == 0">女</el-tag>
|
<el-tag disable-transitions type="warning" v-if="scope.row.sex == 0">女</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="会员" prop="memberName"></el-table-column>
|
<el-table-column label="会员" prop="memberLevelName"></el-table-column>
|
||||||
<el-table-column label="分销员" prop="distributionShops"></el-table-column>
|
<el-table-column label="分销员" prop="distributionShops">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.distributionShops.length > 0">是</span>
|
||||||
|
<span v-else>否</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="手机号" prop="phone" width="150"></el-table-column>
|
<el-table-column label="手机号" prop="phone" width="150"></el-table-column>
|
||||||
<el-table-column label="余额" prop="amount"></el-table-column>
|
<el-table-column label="余额" prop="amount"></el-table-column>
|
||||||
<el-table-column label="积分" prop="accountPoints"></el-table-column>
|
<el-table-column label="积分" prop="accountPoints"></el-table-column>
|
||||||
@@ -45,7 +50,7 @@
|
|||||||
<el-table-column label="注册时间" prop="createTime" width="200"></el-table-column>
|
<el-table-column label="注册时间" prop="createTime" width="200"></el-table-column>
|
||||||
<el-table-column label="操作" width="100" fixed="right">
|
<el-table-column label="操作" width="100" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" :disabled="!!scope.row.isDistribution"
|
<el-button type="primary" :disabled="!!scope.row.distributionShops.length"
|
||||||
@click="selectHandle(scope.row)">选择</el-button>
|
@click="selectHandle(scope.row)">选择</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt14">
|
<div class="row mt14">
|
||||||
<tabHeader v-model="tabIndex" :list="[{ label: '我的邀请', value: 0 }, { label: '收入明细', value: 1 }]"
|
<tabHeader v-model="tabIndex" :list="[{ label: '邀请用户', value: 0 }, { label: '收入明细', value: 1 }]"
|
||||||
@change="tabChange" />
|
@change="tabChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt14">
|
<div class="row mt14">
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<el-form-item>
|
<el-form-item>
|
||||||
<selectUser v-model="querForm.userId" />
|
<selectUser v-model="querForm.userId" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item v-if="tabIndex == 1">
|
||||||
<el-select v-model="querForm.distributionLevelId" placeholder="请选择等级" style="width: 200px;">
|
<el-select v-model="querForm.distributionLevelId" placeholder="请选择等级" style="width: 200px;">
|
||||||
<el-option v-for="item in distributionLevelIdList" :key="item.id" :label="item.name"
|
<el-option v-for="item in distributionLevelIdList" :key="item.id" :label="item.name"
|
||||||
:value="item.id"></el-option>
|
:value="item.id"></el-option>
|
||||||
@@ -61,9 +61,10 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt14">
|
<div class="row mt14">
|
||||||
<el-table :data="tableData.list" stripe border v-loading="tableData.loading">
|
<!-- 邀请用户 -->
|
||||||
<el-table-column label="ID" prop="id" v-if="tabIndex == 0" width="80"></el-table-column>
|
<el-table :data="tableData.list" stripe border v-loading="tableData.loading" v-if="tabIndex == 0">
|
||||||
<el-table-column label="用户" prop="id">
|
<el-table-column label="ID" prop="shopUserId" width="80"></el-table-column>
|
||||||
|
<el-table-column label="用户" prop="shopUserName">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div>{{ scope.row.shopUserName }}</div>
|
<div>{{ scope.row.shopUserName }}</div>
|
||||||
@@ -71,14 +72,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="等级" prop="distributionLevelName" v-if="tabIndex == 0"></el-table-column>
|
<el-table-column label="等级" prop="levelName"></el-table-column>
|
||||||
<!-- <el-table-column label="总消费金额(元)" prop="id"></el-table-column> -->
|
<el-table-column label="总消费金额(元)" prop="oneIncome"></el-table-column>
|
||||||
<el-table-column label="累计收益(元)" prop="totalIncome" v-if="tabIndex == 0"></el-table-column>
|
<el-table-column label="累计收益(元)" prop="totalIncome"></el-table-column>
|
||||||
<el-table-column label="邀请时间" prop="createTime" v-if="tabIndex == 0"></el-table-column>
|
<el-table-column label="邀请时间" prop="inviteTime"></el-table-column>
|
||||||
<el-table-column label="状态" prop="distributionLevelName" v-if="tabIndex == 1"></el-table-column>
|
</el-table>
|
||||||
<el-table-column label="关联订单号" prop="distributionLevelName" v-if="tabIndex == 1"></el-table-column>
|
<!-- 收入明细 -->
|
||||||
<el-table-column label="收益(元)" prop="distributionLevelName" v-if="tabIndex == 1"></el-table-column>
|
<el-table :data="tableData.list" stripe border v-loading="tableData.loading" v-if="tabIndex == 1">
|
||||||
<el-table-column label="创建时间)" prop="distributionLevelName" v-if="tabIndex == 1"></el-table-column>
|
<el-table-column label="用户" prop="nickName">
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="column">
|
||||||
|
<div>{{ scope.row.nickName }}</div>
|
||||||
|
<div>{{ scope.row.phone }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" prop="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag disable-transitions type="success" v-if="scope.row.status == 'success'">
|
||||||
|
已入账
|
||||||
|
</el-tag>
|
||||||
|
<el-tag disable-transitions type="info" v-if="scope.row.status == 'pending'">
|
||||||
|
待入账
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="关联订单号" prop="orderNo"></el-table-column>
|
||||||
|
<el-table-column label="收益(元)" prop="rewardAmount">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ multiplyAndFormat(scope.row.rewardAmount || 0) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" prop="createTime"></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" style="margin-top: 14px;">
|
<div class="row" style="margin-top: 14px;">
|
||||||
@@ -94,7 +119,7 @@ import { ref } from 'vue'
|
|||||||
import tabHeader from '../../components/tabHeader.vue'
|
import tabHeader from '../../components/tabHeader.vue'
|
||||||
import selectUser from '../../components/selectUser.vue'
|
import selectUser from '../../components/selectUser.vue'
|
||||||
import { multiplyAndFormat } from '@/utils'
|
import { multiplyAndFormat } from '@/utils'
|
||||||
import { distributionUserPage, distributionFlowGet, distributionGet } from '@/api/coupon'
|
import { distributionUserInviteUser, distributionFlowGet, distributionGet } from '@/api/coupon'
|
||||||
|
|
||||||
const tabIndex = ref(0)
|
const tabIndex = ref(0)
|
||||||
|
|
||||||
@@ -110,6 +135,7 @@ const querForm = ref({
|
|||||||
const distributionLevelIdList = ref([])
|
const distributionLevelIdList = ref([])
|
||||||
|
|
||||||
function resetHandle() {
|
function resetHandle() {
|
||||||
|
tabIndex.value = 0
|
||||||
querForm.value.userId = ''
|
querForm.value.userId = ''
|
||||||
querForm.value.distributionLevelId = ''
|
querForm.value.distributionLevelId = ''
|
||||||
searchHandle()
|
searchHandle()
|
||||||
@@ -120,6 +146,19 @@ function searchHandle() {
|
|||||||
getTableData()
|
getTableData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusList = ref([
|
||||||
|
{
|
||||||
|
value: 'pending',
|
||||||
|
label: '待入账',
|
||||||
|
type: 'info'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'success',
|
||||||
|
label: '已入账',
|
||||||
|
type: 'success'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
const tableData = reactive({
|
const tableData = reactive({
|
||||||
loading: false,
|
loading: false,
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -152,13 +191,11 @@ async function getTableData() {
|
|||||||
let res = ''
|
let res = ''
|
||||||
|
|
||||||
if (tabIndex.value == 0) {
|
if (tabIndex.value == 0) {
|
||||||
res = await distributionUserPage({
|
res = await distributionUserInviteUser({
|
||||||
id: rowInfo.value.id,
|
id: rowInfo.value.id,
|
||||||
parentId: rowInfo.value.id,
|
|
||||||
page: tableData.page,
|
page: tableData.page,
|
||||||
size: tableData.size,
|
size: tableData.size,
|
||||||
shopUserId: querForm.value.userId,
|
shopUserId: querForm.value.userId
|
||||||
distributionLevelId: querForm.value.distributionLevelId
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
res = await distributionFlowGet({
|
res = await distributionFlowGet({
|
||||||
|
|||||||
@@ -46,7 +46,13 @@
|
|||||||
<el-table-column label="开通时间" prop="createTime"></el-table-column>
|
<el-table-column label="开通时间" prop="createTime"></el-table-column>
|
||||||
<el-table-column label="操作" width="300" fixed="right">
|
<el-table-column label="操作" width="300" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button link type="primary" @click="editorUserDialogRef.show(1, scope.row)">更改分销组</el-button>
|
<el-button link type="primary" @click="editorUserDialogRef.show(1, scope.row)"
|
||||||
|
v-if="scope.row.isAssignLevel === 0">
|
||||||
|
更改分销组
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="primary" @click="editorUserDialogRef.show(3, scope.row)" v-else>
|
||||||
|
重置分销组
|
||||||
|
</el-button>
|
||||||
<el-button link type="primary" @click="editorUserDialogRef.show(2, scope.row)">取消分销员</el-button>
|
<el-button link type="primary" @click="editorUserDialogRef.show(2, scope.row)">取消分销员</el-button>
|
||||||
<el-button link type="primary" @click="distributionUserDetailRef.show(scope.row)">分销详情</el-button>
|
<el-button link type="primary" @click="distributionUserDetailRef.show(scope.row)">分销详情</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
<span>是否确认取消分销员</span>
|
<span>是否确认取消分销员</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="type == 3">
|
||||||
|
<div class="center">
|
||||||
|
<el-icon color="red" size="24">
|
||||||
|
<Warning />
|
||||||
|
</el-icon>
|
||||||
|
<span>是否确认重置分销组<br />重置后将会按照用户的实际数据匹配分销组</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="visible = false">取 消</el-button>
|
<el-button @click="visible = false">取 消</el-button>
|
||||||
@@ -28,7 +36,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { distributionUser, distributionGet } from '@/api/coupon'
|
import { distributionUser, distributionGet, distributionUserResetLevel } from '@/api/coupon'
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const rowInfo = ref('')
|
const rowInfo = ref('')
|
||||||
@@ -49,16 +57,24 @@ const form = ref({
|
|||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
async function submitHandle() {
|
async function submitHandle() {
|
||||||
try {
|
try {
|
||||||
|
confirmLoading.value = true
|
||||||
let data = {}
|
let data = {}
|
||||||
data.id = rowInfo.value.id
|
data.id = rowInfo.value.id
|
||||||
if (type.value == 1) {
|
if (type.value == 1) {
|
||||||
|
data.isAssignLevel = 1
|
||||||
data.distributionLevelId = form.value.distributionLevelId
|
data.distributionLevelId = form.value.distributionLevelId
|
||||||
data.distributionLevelName = levelConfigList.value.find(item => item.id == form.value.distributionLevelId).name
|
data.distributionLevelName = levelConfigList.value.find(item => item.id == form.value.distributionLevelId).name
|
||||||
|
|
||||||
|
await distributionUser(data, 'put')
|
||||||
} else if (type.value == 2) {
|
} else if (type.value == 2) {
|
||||||
data.status = 9
|
data.status = 9
|
||||||
|
|
||||||
|
await distributionUser(data, 'put')
|
||||||
|
} else if (type.value == 3) {
|
||||||
|
data.shopId = rowInfo.value.shopId
|
||||||
|
|
||||||
|
await distributionUserResetLevel(data)
|
||||||
}
|
}
|
||||||
confirmLoading.value = true
|
|
||||||
await distributionUser(data, 'put')
|
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: '注意',
|
title: '注意',
|
||||||
message: '保存成功',
|
message: '保存成功',
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
<el-form-item label="获得佣金条件" prop="inviteCount">
|
<el-form-item label="获得佣金条件" prop="inviteCount">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<el-input v-model="form.inviteCount" placeholder="请输入" :maxlength="8" style="width: 300px;"
|
<el-input v-model="form.inviteCount" placeholder="请输入" :maxlength="8" style="width: 300px;"
|
||||||
@input="e => form.inviteCount = filterNumberInput(e, 1)"></el-input>
|
@input="e => form.inviteCount = filterNumberInput(e, 1)">
|
||||||
|
<template #append>人</template>
|
||||||
|
</el-input>
|
||||||
<div class="tips">邀请达到指定人数才可赚取佣金</div>
|
<div class="tips">邀请达到指定人数才可赚取佣金</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
{{ index + 1 }}级:{{ item.name || '请输入名称' }}
|
{{ index + 1 }}级:{{ item.name || '请输入名称' }}
|
||||||
<div class="del">
|
<div class="del">
|
||||||
<el-icon @click="form.levelConfigList.splice(index, 1)">
|
<el-icon v-if="index > 0" @click="form.levelConfigList.splice(index, 1)">
|
||||||
<Delete />
|
<Delete />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +95,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form_wrap_title" v-if="form.upgradeType != 'not_upgrade'">升级条件</div>
|
<div class="form_wrap_title">
|
||||||
|
<span v-if="form.upgradeType != 'not_upgrade'">升级条件</span>
|
||||||
|
</div>
|
||||||
<div class="form_wrap_row">
|
<div class="form_wrap_row">
|
||||||
<div class="column" v-if="form.upgradeType == 'invite'">
|
<div class="column" v-if="form.upgradeType == 'invite'">
|
||||||
<div class="center">
|
<div class="center">
|
||||||
@@ -101,8 +105,8 @@
|
|||||||
<span class="required">*</span>有效人数达
|
<span class="required">*</span>有效人数达
|
||||||
</div>
|
</div>
|
||||||
<div class="ipt">
|
<div class="ipt">
|
||||||
<el-input v-model="item.inviteCount" placeholder="请输入" style="width: 200px;"
|
<el-input v-model="item.inviteCount" placeholder="请输入" :disabled="index === 0"
|
||||||
@input="e => item.inviteCount = filterNumberInput(e, 0)">
|
style="width: 200px;" @input="e => item.inviteCount = filterNumberInput(e, 0)">
|
||||||
<template #append>人</template>
|
<template #append>人</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,8 +119,8 @@
|
|||||||
<span class="required">*</span>消费金额达
|
<span class="required">*</span>消费金额达
|
||||||
</div>
|
</div>
|
||||||
<div class="ipt">
|
<div class="ipt">
|
||||||
<el-input v-model="item.costAmount" placeholder="请输入" style="width: 200px;"
|
<el-input v-model="item.costAmount" placeholder="请输入" :disabled="index === 0"
|
||||||
@input="e => item.costAmount = filterNumberInput(e)">
|
style="width: 200px;" @input="e => item.costAmount = filterNumberInput(e)">
|
||||||
<template #append>元</template>
|
<template #append>元</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,7 +161,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<el-form-item style="margin-top: 24px;">
|
<el-form-item style="margin-top: 24px;">
|
||||||
<el-button type="primary" @click="form.levelConfigList.push({ ...levelConfigListObj })">添加等级</el-button>
|
<el-button type="primary" @click="addLevelHandle">添加等级</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div class="title_row mt14">未开通页面营销</div>
|
<div class="title_row mt14">未开通页面营销</div>
|
||||||
<el-form-item :label-width="0" style="margin-top: 14px;">
|
<el-form-item :label-width="0" style="margin-top: 14px;">
|
||||||
@@ -278,7 +282,7 @@ const rules = ref({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (item.levelOneCommission === '' || item.levelOneCommission == 0) {
|
if (item.levelOneCommission === '' || item.levelOneCommission == 0) {
|
||||||
tips = `请输入${index + 1}级的订单一级分成`
|
tips = `请输入${index + 1}级的分成比例`
|
||||||
callback(new Error(tips))
|
callback(new Error(tips))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -296,13 +300,27 @@ const rules = ref({
|
|||||||
|
|
||||||
// 条件升级切换
|
// 条件升级切换
|
||||||
function upgradeTypeChange(e) {
|
function upgradeTypeChange(e) {
|
||||||
form.value.levelConfigList = [{ ...levelConfigListObj.value }]
|
form.value.levelConfigList = []
|
||||||
|
addLevelHandle()
|
||||||
|
// form.value.levelConfigList = [{ ...levelConfigListObj.value }]
|
||||||
// if (e == 'not_upgrade') {
|
// if (e == 'not_upgrade') {
|
||||||
// form.value.levelConfigList = []
|
// form.value.levelConfigList = []
|
||||||
// } else {
|
// } else {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addLevelHandle() {
|
||||||
|
form.value.levelConfigList.push({ ...levelConfigListObj.value })
|
||||||
|
|
||||||
|
console.log('form.value.levelConfigList===', form.value.levelConfigList);
|
||||||
|
|
||||||
|
|
||||||
|
if (form.value.levelConfigList.length == 1) {
|
||||||
|
form.value.levelConfigList[0].inviteCount = 0
|
||||||
|
form.value.levelConfigList[0].costAmount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提交
|
// 提交
|
||||||
function submintHandle() {
|
function submintHandle() {
|
||||||
console.log(form.value);
|
console.log(form.value);
|
||||||
@@ -329,6 +347,9 @@ async function distributionGetAjax() {
|
|||||||
const res = await distributionGet()
|
const res = await distributionGet()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
form.value = { ...res }
|
form.value = { ...res }
|
||||||
|
if (form.value.rewardCount == -1) {
|
||||||
|
isLimitCount.value = 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -405,6 +426,9 @@ onMounted(async () => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.form_wrap_title {
|
.form_wrap_title {
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
Reference in New Issue
Block a user