Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
@@ -38,7 +38,7 @@ export function StandardsInfoPage (data) {
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 车型库清单的下拉列表
|
||||
export function solostarData (data) {
|
||||
return axios({
|
||||
url: 'api/sarStandProjectLibrary/solostar',
|
||||
@@ -46,3 +46,11 @@ export function solostarData (data) {
|
||||
params: data
|
||||
})
|
||||
}
|
||||
// 活动日历查询接口
|
||||
export function meetingByMonth (params) {
|
||||
return axios({
|
||||
url: 'api/sapMeetInfo/getMeetingsByMonthType',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -244,6 +244,11 @@ export default {
|
||||
path: '/standContrast',
|
||||
menuId: '1d83a6278f781419f628e9f5560f5c86'
|
||||
},
|
||||
{
|
||||
title: '标准化活动日历',
|
||||
path: '/standCalendar',
|
||||
menuId: '0ea66e3dd36873082b6851f69e92c8be'
|
||||
},
|
||||
// {
|
||||
// title: '标准比对库',
|
||||
// path: '/standardComparisonLibrary',
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
<!--标准化活动日历-->
|
||||
<template>
|
||||
<div class="standCalendar">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div class="header">
|
||||
<div class="date-picker" style="display: inline">
|
||||
<span class="item">
|
||||
<el-select v-model="formData.year" @change="handleDate">
|
||||
<el-option v-for="item in year" :value="item">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</span>
|
||||
<span class="item">
|
||||
<el-select v-model="formData.month" @change="handleDate">
|
||||
<el-option v-for="item in month" :value="item">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</span>
|
||||
</div>
|
||||
<div style="float: right;display: inline;height: 30px;">
|
||||
<el-button size="mini" @click="today">今天</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-calendar v-model="value">
|
||||
<template slot="dateCell" slot-scope="{date, data}">
|
||||
<div @click="calendarOnClick(data)">
|
||||
<div class="calendar-day" style="text-align: center;">
|
||||
<span class="everyDay" style="line-height: 40px">
|
||||
{{ data.day.split('-').slice(2).join('-') }}
|
||||
<br/>
|
||||
<!-- 获取当前登录人的会议并表示出来-->
|
||||
<span v-for="item in dealMyDate(data.day)" :value="item.fillPeopleId">
|
||||
<el-tag class="tagOne"/>
|
||||
<el-tag v-if="userId === item.fillPeopleId" class="tagTwo"/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card style="height: 655px;overflow: auto">
|
||||
<div class="title_time">{{ timeDay.day || nowDate }}</div>
|
||||
<el-card class="card_info_empty " v-if="todayData.length === 0">
|
||||
<div style="text-align: center">
|
||||
<img :src="require('@/assets/images/meeting.png')">
|
||||
</div>
|
||||
<div style="text-align: center;font-weight: bold;color: #99999a">暂无会议</div>
|
||||
</el-card>
|
||||
<el-card class="card_info" v-else v-for="item in todayData" :value="item">
|
||||
<span v-for="res in item.attend" :key="res.fillPeopleId">
|
||||
<el-tag v-if="userId === res.fillPeopleId" type="success" size="mini">会议</el-tag>
|
||||
<el-tag v-else type="warning" size="mini">会议</el-tag>
|
||||
</span>
|
||||
<i style="margin-left: 10px;font-weight: bold">{{ item.title }}</i>
|
||||
<div class="content_text">
|
||||
<span>会议时间 : {{ $moment(item.date).format('YYYY-MM-DD HH:mm') }} </span>
|
||||
</div>
|
||||
<div class="content_text">
|
||||
<span>参与人 : {{ item.partPeople.join('、') }} </span>
|
||||
</div>
|
||||
<div class="content_text2">
|
||||
<span>会议内容 : {{ item.content }}</span>
|
||||
</div>
|
||||
<div class="content_text">
|
||||
<span>会议附件 :
|
||||
<span v-if="item.files.length === '0'">{{ '-' }}</span>
|
||||
<span v-for="res in item.files" :key="res.id" style="color: #409EFF;" @click="previews(res.id)">
|
||||
{{ res.name }} {{ ';'}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { meetingByMonth } from '@/api/unqualProcess.js'
|
||||
|
||||
export default {
|
||||
name: "calendar",
|
||||
data() {
|
||||
return {
|
||||
userId: this.$store.getters.userInfo.userId,
|
||||
year: [],
|
||||
month: [],
|
||||
formData: {
|
||||
year: '',
|
||||
month: '',
|
||||
},
|
||||
//点击日历获取当天会议数据
|
||||
todayData: [],
|
||||
// 全部会议数据
|
||||
resDate: [],
|
||||
// 点击日历获取暂时储存数组
|
||||
timeDay: {},
|
||||
|
||||
// 上次点击的日期
|
||||
lastYear: '',
|
||||
lastMonth: '',
|
||||
// 日历当前时间
|
||||
value: new Date(),
|
||||
// 当前时间
|
||||
nowDate: '',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
/** 获取当前日期*/
|
||||
let yy = new Date().getFullYear();
|
||||
let mm = new Date().getMonth() + 1;
|
||||
let dd = new Date().getDate();
|
||||
if (mm<10) {
|
||||
mm = this.padding0(mm, 2)
|
||||
}
|
||||
this.nowDate = yy + '-' + mm + '-' + dd
|
||||
this.formData.year = yy + '年'
|
||||
this.formData.month = mm + '月'
|
||||
this.lastYear = yy
|
||||
this.lastMonth = mm
|
||||
this.getMonthData()
|
||||
this.init()
|
||||
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getYear();
|
||||
this.getMonth();
|
||||
},
|
||||
getYear() {//获取年
|
||||
let date = new Date;
|
||||
let current_year = date.getFullYear();
|
||||
for (let i = -2; i < 3; i++) {
|
||||
let y = current_year - i;
|
||||
this.year.push(y + '年');
|
||||
}
|
||||
},
|
||||
getMonth() {//获取月
|
||||
for (let i = 1; i < 13; i++) {
|
||||
this.month.push(i + '月');
|
||||
}
|
||||
},
|
||||
/** 获取当前月日历数据*/
|
||||
getMonthData() {
|
||||
let data = {
|
||||
date: this.nowDate,
|
||||
type: 1
|
||||
}
|
||||
meetingByMonth(data).then(res => {
|
||||
if (res.length !== 0) {
|
||||
// 对传参的数据转义
|
||||
res.forEach(item => {
|
||||
item.attend = JSON.parse(item.attend)
|
||||
item.files = JSON.parse(item.files)
|
||||
this.$set(item, 'partPeople', [])
|
||||
})
|
||||
|
||||
res.forEach(item => {
|
||||
item.attend.forEach(msgAtt => {
|
||||
item.partPeople.push(msgAtt.fillPeople)
|
||||
})
|
||||
})
|
||||
this.resDate = res
|
||||
this.dealMyDate()
|
||||
this.todayData = []
|
||||
this.resDate.forEach(item=>{
|
||||
if (item.date.slice(0,10) === this.nowDate) {
|
||||
this.todayData.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 获取当前点击日历表格数据*/
|
||||
calendarOnClick(val) {
|
||||
this.todayData = []
|
||||
this.timeDay = val
|
||||
if (this.resDate.length !== 0) {
|
||||
this.resDate.forEach(item => {
|
||||
if (item.date.slice(0, 10) === this.timeDay.day) {
|
||||
this.todayData.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 点击日历头部日期变化
|
||||
this.formData.year = val.day.slice(0, 4) + '年'
|
||||
if (val.day.slice(5, 6) === '0') {
|
||||
this.formData.month = val.day.slice(6, 7) + '月'
|
||||
} else {
|
||||
this.formData.month = val.day.slice(5, 7) + '月'
|
||||
}
|
||||
},
|
||||
/** 点击今天按钮 */
|
||||
today() {
|
||||
this.value = new Date()
|
||||
let yy = new Date().getFullYear();
|
||||
let mm = new Date().getMonth() + 1;
|
||||
let dd = new Date().getDate();
|
||||
this.formData.year = yy + '年'
|
||||
this.formData.month = mm + '月'
|
||||
this.nowDate = yy + '-' + mm + '-' + dd
|
||||
this.getMonthData()
|
||||
},
|
||||
/** 获取所有会议的时间及内容
|
||||
* 并在日历上显示出标记*/
|
||||
dealMyDate(v) {
|
||||
let len = this.resDate.length
|
||||
let res = ''
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (this.resDate[i].date.slice(0, 10) === v) {
|
||||
res = this.resDate[i].attend
|
||||
break
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
// 头部时间改变事件
|
||||
handleDate() {
|
||||
let selYear = this.formData.year.slice(0, 4)
|
||||
let selMonth = ''
|
||||
if (this.formData.month.slice(-3, -2) === '') {
|
||||
selMonth = this.formData.month.slice(0, 1)
|
||||
} else {
|
||||
selMonth = this.formData.month.slice(0, 2)
|
||||
}
|
||||
let birthday = selYear + '-' + this.padding0(selMonth, 2) + '-' + '01'
|
||||
this.nowDate = birthday
|
||||
this.getMonthData()
|
||||
// 根据头部日期调整,日历变化
|
||||
let yearNum = ''
|
||||
let monthNum = ''
|
||||
if (this.lastYear !== selYear) {
|
||||
yearNum = this.lastYear - selYear
|
||||
} else {
|
||||
yearNum = 0
|
||||
}
|
||||
if (this.lastMonth !== selMonth) {
|
||||
monthNum = this.lastMonth - selMonth
|
||||
} else {
|
||||
monthNum = 0
|
||||
}
|
||||
this.lastYear = selYear
|
||||
this.lastMonth = selMonth
|
||||
this.value = new Date(this.value.setMonth(this.value.getMonth() - monthNum))
|
||||
this.value = new Date(this.value.setFullYear(this.value.getFullYear() - yearNum))
|
||||
},
|
||||
//数字前填充0方法
|
||||
padding0(num, length) {
|
||||
for (let len = (num + '').length; len < length; len = num.length) {
|
||||
num = '0' + num
|
||||
}
|
||||
return num
|
||||
},
|
||||
/** 点击对应附件进行下载*/
|
||||
previews(val) {
|
||||
let attId = val
|
||||
if (attId != null && attId !== "") {
|
||||
window.location.href = "/api/att/attFile/downloadFileForSar?fileId=" + attId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/assets/styles/mixins';
|
||||
@import '~@/assets/styles/style';
|
||||
|
||||
.standCalendar {
|
||||
.date-picker {
|
||||
.item {
|
||||
/deep/ .el-input {
|
||||
width: 88px;
|
||||
}
|
||||
|
||||
/deep/ .el-input__icon {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
/deep/ .el-input--suffix .el-input__inner {
|
||||
height: 30px;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.el-calendar-table td.is-selected {
|
||||
background-color: #f9e3e3;
|
||||
}
|
||||
/deep/ .el-calendar__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/deep/ .el-calendar-table thead th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tagOne {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
padding: 0;
|
||||
background-color: #e6a23c
|
||||
}
|
||||
|
||||
.tagTwo {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
padding: 0;
|
||||
background-color: #67c23a
|
||||
}
|
||||
|
||||
.title_time {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/deep/ .el-card__body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card_info_empty {
|
||||
height: 500px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.card_info {
|
||||
height: 190px;
|
||||
margin-top: 10px;
|
||||
|
||||
.content_text {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
line-height: normal;
|
||||
margin-top: 10px;
|
||||
font-size: 5px;
|
||||
}
|
||||
|
||||
.content_text2 {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: normal;
|
||||
margin-top: 10px;
|
||||
font-size: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -259,6 +259,7 @@ export default {
|
||||
active: '1',
|
||||
|
||||
ch_pageData: {
|
||||
fillPeopleId: '',
|
||||
fillPeople: '',
|
||||
fillUnit: '',
|
||||
fillDept: '',
|
||||
@@ -326,6 +327,7 @@ export default {
|
||||
getFormFieldList (item) {
|
||||
this.$nextTick(() => {
|
||||
this.ch_pageData = JSON.parse(JSON.stringify(item)).ch_pageData
|
||||
this.ch_pageData.fillPeopleId = this.$store.getters.userInfo.userId
|
||||
this.ch_pageData.fillPeople = this.$store.getters.userInfo.userName
|
||||
this.roleForm = JSON.parse(JSON.stringify(item)).roleList
|
||||
// this.rh_pageData.comityFileNum = this.fileTextList
|
||||
|
||||
@@ -276,6 +276,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.ch_pageData = JSON.parse(JSON.stringify(item)).ch_pageData
|
||||
this.ch_pageData.myPartRole = [{
|
||||
fillPeopleId: this.ch_pageData.fillPeopleId,
|
||||
fillPeople: this.ch_pageData.fillPeople,
|
||||
fillUnit: this.ch_pageData.fillUnit,
|
||||
fillDept: this.ch_pageData.fillDept,
|
||||
|
||||
+162
-1
@@ -148,7 +148,7 @@
|
||||
clearable
|
||||
/>
|
||||
<!-- :disabled="this.addOrEdit !== 'add'"-->
|
||||
<el-button type="primary" size="mini" @click="">手动查找</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleSelect">手动查找</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="标准名称/政策名称"
|
||||
@@ -267,6 +267,116 @@
|
||||
>
|
||||
</div>
|
||||
</el-drawer>
|
||||
<el-drawer
|
||||
:title="handleSelectTitle"
|
||||
:wrapperClosable="false"
|
||||
:visible.sync="selectModal"
|
||||
size="1250px"
|
||||
:styles="styles"
|
||||
:close="cancelSelectModel">
|
||||
<div class="demo-drawer-content">
|
||||
<el-form
|
||||
class="label-input-form"
|
||||
style="margin-top: 15px;"
|
||||
label-width="130px"
|
||||
:modal="handleSelectForm"
|
||||
:inline="true"
|
||||
>
|
||||
<el-form-item label="标准/政策来源" class="search-item">
|
||||
<el-select
|
||||
v-model="handleSelectForm.dataSource"
|
||||
:popper-append-to-body="false"
|
||||
placeholder="请选择标准/政策来源">
|
||||
<el-option value="INLAND_STAND" label="国内标准库"></el-option>
|
||||
<el-option value="FOREIGN_STAND" label="海外标准库"></el-option>
|
||||
<el-option value="FOREIGN_LAWS" label="国内外政策库"></el-option>
|
||||
<el-option value="BUSINESS_STAND" label="企业标准库"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标准编号/政策编号" class="search-item">
|
||||
<el-input
|
||||
v-model="handleSelectForm.lawNumber"
|
||||
placeholder="根据标准编号/政策编号查找"
|
||||
clearable
|
||||
:maxlength="100"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="标准名称/政策名称" class="search-item">
|
||||
<el-input
|
||||
v-model="handleSelectForm.lawName"
|
||||
placeholder="根据标准名称/政策名称查找"
|
||||
clearable
|
||||
:maxlength="100"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="search-item btn-box">
|
||||
<el-button
|
||||
icon="el-icon-search"
|
||||
type="primary"
|
||||
class="common-button-primary"
|
||||
round
|
||||
@click="searchLawBtn">
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="search-item btn-box">
|
||||
<el-button
|
||||
class="common-button-default"
|
||||
icon="el-icon-refresh-left"
|
||||
round
|
||||
type="default"
|
||||
@click="clearLawBtn"></el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
ref="selections"
|
||||
tooltip-effect="dark"
|
||||
style="width: 100%;overflow-y: auto;overflow-x: hidden;"
|
||||
border
|
||||
:height="300"
|
||||
:header-cell-style="{background: '#e8e8e8', color: '#333333', fontSize: '16px',
|
||||
fontWeight: 'bold', height: '48px'}"
|
||||
@selection-change="handleSelectTableChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
align="center">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="350"
|
||||
align="center"
|
||||
label="标准编号/政策编号">
|
||||
<template slot-scope="scope">
|
||||
<a v-if="scope.row.standYear" class="table-jump">{{ scope.row.standSort }} {{ scope.row.standNumber }}-{{ scope.row.standYear }}</a>
|
||||
<a v-else class="table-jump">{{ scope.row.standSort }} {{ scope.row.standNumber }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="lawName"
|
||||
align="center"
|
||||
label="标准名称/政策名称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="textStatus"
|
||||
align="center"
|
||||
label="标准状态"
|
||||
width="350">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ textStatusMap[scope.row.textStatus] }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--分页-->
|
||||
<pagination
|
||||
style="position: relative;"
|
||||
:page="handleSelectPage"
|
||||
:total="handleSelectTotal"
|
||||
@pageChange="handleSelectPageChange"
|
||||
@pageSizeChange="handleSelectPageSizeChange"></pagination>
|
||||
<div slot="footer" class="demo-drawer-footer">
|
||||
<el-button class="common-button-default" round icon="el-icon-close" @click="selectModal = false">取消</el-button>
|
||||
<el-button type="primary" round class="common-button-primary" icon="el-icon-check" @click="handleSelectConfirm">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -275,6 +385,26 @@ export default {
|
||||
name: "listTracking",
|
||||
data () {
|
||||
return {
|
||||
handleSelectPage: 1,
|
||||
handleSelectTotal: 0,
|
||||
textStatusMap: {}, // 文本状态id与name对应
|
||||
handleSelectTableList:[],
|
||||
handleSelectForm: {
|
||||
dataSource: '',
|
||||
lawNumber: '', // 标准编号/政策编号
|
||||
lawName: '', // 标准名称/政策名称
|
||||
page: 1,
|
||||
pageSize: this.$store.getters.userInfo.configContent,
|
||||
total: 0,
|
||||
standType: 'INLAND',
|
||||
quoteIdList: '',
|
||||
validFlag: '0',
|
||||
menuId: 'nomenu'
|
||||
},
|
||||
//手动查找模态框的标题
|
||||
handleSelectTitle: '手动查找',
|
||||
//手动查找的模态框
|
||||
selectModal: false,
|
||||
submitLoading: false,
|
||||
attrTypeOptions: [
|
||||
{ label: '下拉选项', value: 'SEL_OPTION' },
|
||||
@@ -398,6 +528,20 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSelectPageChange(page){
|
||||
|
||||
},
|
||||
handleSelectPageSizeChange(pageSize){
|
||||
|
||||
},
|
||||
// 手动查找 table选择事件
|
||||
handleSelectTableChange (row) {
|
||||
this.handleSelectTableList = row
|
||||
},
|
||||
//点击手动查找按钮
|
||||
handleSelect() {
|
||||
this.selectModal = true
|
||||
},
|
||||
saveInfo () {
|
||||
this.$refs['attributeEO'].validate((valid) => {
|
||||
if (valid) {
|
||||
@@ -448,6 +592,20 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
cancelSelectModel () {
|
||||
// this.$nextTick(() => {
|
||||
// this.$refs['attributeEO'].resetFields()
|
||||
// this.attributeEO = {
|
||||
// id: '',
|
||||
// lawId: '',
|
||||
// lawName: '',
|
||||
// productType: '',
|
||||
// implTime: '',
|
||||
// implRequire: '',
|
||||
// }
|
||||
// })
|
||||
this.selectModal = false
|
||||
},
|
||||
cancelModel () {
|
||||
this.$nextTick(() => {
|
||||
this.$refs['attributeEO'].resetFields()
|
||||
@@ -639,6 +797,9 @@ export default {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/deep/.el-form-item__label{
|
||||
color: #333333;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1589,6 +1589,17 @@ const routes = [
|
||||
menuId: 'B8UYM3WFCMGXR2BPF9JA'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/standCalendar',
|
||||
name: 'calendar',
|
||||
component: () =>
|
||||
import('@/pages/localTool/standCalendar'),
|
||||
meta: {
|
||||
requireAuth: false,
|
||||
title: '标准化活动日历',
|
||||
menuId: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/itemSplitInfo',
|
||||
name: 'ItemSplitInfo',
|
||||
|
||||
Reference in New Issue
Block a user