update code

This commit is contained in:
manhlab
2021-04-07 06:32:42 -04:00
parent 7fb98911a6
commit a4753625f6
779 changed files with 335717 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
<template>
<CRow>
<CCol sm="3">
<CSelect :options="fields" @update:value="fieldChanged" />
</CCol>
<CCol sm="9">
<CInput placeholder="Tìm kiếm" :value.sync="value" @update:value="valueChanged" />
</CCol>
</CRow>
</template>
<script>
export default {
name: "SearchBox",
props: {
fields: {
type: Array,
default: function () {
return [{ value: "", label: "Tất cả" }];
},
},
},
data() {
return {
field: String,
value: "",
searching: {},
};
},
watch: {
field: {
handler(value) {
this.$emit("fieldChanged", value);
this.value = value.defaultValue;
this.fireSearching();
},
},
value: {
handler(value) {
this.$emit("valueChanged", value);
this.fireSearching();
},
},
searching: {
handler(value) {
this.$emit("searching", value);
},
}
},
methods: {
fieldChanged(field) {
this.field = this.fields.find((e) => {
return e.value == field;
});
},
valueChanged(value) {
this.value = value;
},
fireSearching(){
this.searching = {field: this.field.value, value: this.value};
}
},
};
</script>

View File

@@ -0,0 +1,129 @@
<template>
<CCard>
<CCardHeader>
<strong>Tệp đính kèm</strong>
</CCardHeader>
<CCardBody>
<CDataTable :loading="loading" :items="attachments" :fields="fields">
<template #id="{item, index}">
<td>
<CButton size="sm" @click="downloadAttachment(item)" variant="outline" color="primary">
<CIcon name="cil-cloud-download" />
</CButton>
<CButton
size="sm"
@click="deleteAttachment(item.id, index)"
variant="outline"
color="danger"
>
<CIcon name="cil-remove" />
</CButton>
</td>
</template>
</CDataTable>
<CInputFile custom v-on:change="handleFileUpload" />
</CCardBody>
</CCard>
</template>
<script>
import services from "../../services/factory";
export default {
name: "Attachments",
props: {
documentId: {
required: true
}
},
data() {
return {
loading: true,
fields: [
{ key: "name", label: "Tên", _classes: "w-50" },
{ key: "size", label: "Kích thước (KB)" },
{ key: "downloads", label: "Lượt tải" },
{ key: "id", label: "Hành động" }
],
attachments: [
{
id: "",
name: "",
size: 0,
downloads: 0
}
],
file: {}
};
},
watch: {
documentId: {
handler() {
this.init();
}
}
},
created() {
this.init();
},
methods: {
init() {
this.fetch();
},
async fetch() {
const attachmentResponse = await services.attachment.all({
search: `document_id:${this.documentId}`
});
this.attachments = this.formatKeys(attachmentResponse.data, {
id: "id",
name: "name"
});
this.loading = false;
},
downloadAttachment(item) {
services.attachment
.download(item.id, item.name)
.then(response => {
item.downloads++;
this.$toast.success("Đã tải xuống");
})
.catch(error => {
this.toastHttpError(error);
});
},
deleteAttachment(id, index) {
services.attachment
.delete(id)
.then(response => {
this.attachments.splice(index, 1);
this.$toast.success("Đã xóa");
})
.catch(error => {
this.toastHttpError(error);
});
},
handleFileUpload(files, e) {
this.file = files[0];
this.uploadAttachment();
},
uploadAttachment() {
let formData = new FormData();
formData.append("attachments", this.file);
formData.append("document_id", this.documentId);
services.attachment
.create(formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(response => {
this.attachments.push(response.data);
this.$toast.success("Đã tải lên");
})
.catch(error => {
this.toastHttpError(error);
});
}
}
};
</script>

View File

@@ -0,0 +1,157 @@
<template>
<CCard>
<CCardHeader>
<strong>Văn bản liên kết</strong>
</CCardHeader>
<CCardBody>
<CRow class="form-group">
<CCol v-if="isOutcome" sm="12">
<label>Văn bản đến</label>
<treeselect
v-model="linkToDocument"
:multiple="false"
:options="documents"
@search-change="fetchDocuments"
@select="linkTo"
@input="unLinkTo"
>
<div
slot="option-label"
slot-scope="{ node }"
>{{ node.raw.label + ' - ' + node.raw.abstract || getSymbol(node.raw.id) }}</div>
</treeselect>
</CCol>
<CCol v-else sm="12">
<label>Danh sách đã liên kết</label>
<treeselect
v-model="documentsLinked"
:options="linkedDocuments"
:multiple="true"
:openOnClick="false"
:clearable="false"
disabled
placeholder
>
<CButton
@click="redirectToDocument(node.raw.id)"
class="btn-link p-0"
slot="value-label"
size="sm"
variant="ghost"
slot-scope="{ node }"
>{{ node.raw.label }}</CButton>
</treeselect>
</CCol>
</CRow>
</CCardBody>
</CCard>
</template>
<script>
import services from "../../services/factory";
// import the component
import { Treeselect, ASYNC_SEARCH } from "@riophae/vue-treeselect";
// import the styles
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "CLinkDocument",
props: {
documentId: {
required: true
},
isOutcome: {
type: Boolean,
default: false
}
},
components: { Treeselect },
data() {
return {
// define the default value
linkToDocument: null,
documentsLinked: [],
// define options
documents: [],
linkedDocuments: [],
document: {}
};
},
watch: {
documentId: {
handler() {
this.init();
}
}
},
created() {
this.init();
},
methods: {
async init() {
this.fetchDocuments();
this.fetchDocument();
},
async fetchDocument() {
const documentResponse = await services.document.get(this.documentId, {
with: "linkTo;linked"
});
this.document = documentResponse.data;
this.linkToDocument = this.document.link_to
? this.document.link_to.id
: null;
this.documents.push(this.linkToDocument);
this.linkedDocuments = this.formatKeys(this.document.linked, {
id: "id",
symbol: "label"
});
this.documentsLinked = this.document.linked.map(d => d.id);
},
fetchDocuments(query = "") {
const documentsResponse = services.document
.all({ search: `symbol:${query};book_id:1`, searchJoin: "and", orderBy: "created_at", sortedBy: "desc", })
.then(response => {
const documents = this.formatKeys(response.data.data, {
id: "id",
symbol: "label"
});
this.documents = documents;
});
},
getSymbol(id) {
this.fetchSymbol(id);
return "Không có quyền truy cập";
},
async fetchSymbol(id) {
const response = await services.document.get(id);
if (!response.data.id) return;
const document = {
id: response.data.id,
label: response.data.symbol
};
if (this.documents.map(item => item.id).includes(document.id)) return;
this.documents.push(document);
},
linkTo(document) {
services.document
.update({ link_id: document.id }, this.documentId)
.catch(error => {
this.toastHttpError(error);
});
},
unLinkTo(document) {
if(document === undefined){
services.document
.update({ link_id: null }, this.documentId)
.catch(error => {
this.toastHttpError(error);
});
}
},
redirectToDocument(id) {
this.$emit("redirectTo", id);
}
}
};
</script>

View File

@@ -0,0 +1,305 @@
<template>
<CCard>
<CCardHeader>
<strong v-if="documentId">Chi tiết văn bản</strong>
<strong v-else>Tạo văn bản</strong>
</CCardHeader>
<CCardBody>
<CForm>
<CRow class="form-group">
<CCol sm="6">
<CSelect
class="mb-0"
label="Sổ văn bản"
:options="books"
:value.sync="document.book_id"
placeholder="Please select"
/>
</CCol>
<CCol sm="6">
<CFormGroup class="form-group mb-0">
<template #label>
<slot name="label">
<label>Loại văn bản</label>
</slot>
</template>
<template #input>
<treeselect
v-model="document.type_id"
:multiple="false"
:options="types"
:clearable="false"
></treeselect>
</template>
</CFormGroup>
</CCol>
</CRow>
<CRow class="form-group">
<CCol sm="12">
<CInput label="Số ký hiệu" :value.sync="document.symbol" class="mb-0" />
</CCol>
</CRow>
<CRow class="form-group">
<CCol sm="6">
<CFormGroup class="form-group mb-0">
<template #label>
<slot name="label">
<label>Người soạn</label>
</slot>
</template>
<template #input>
<treeselect
v-model="document.writer_id"
:multiple="false"
:options="writers"
@search-change="fetchWriters"
@input="onClearWriter"
>
<label
slot="option-label"
slot-scope="{ node }"
>{{ node.raw.department ? node.raw.label + ' - ' + node.raw.department.name : node.raw.label }}</label>
</treeselect>
</template>
</CFormGroup>
</CCol>
<CCol sm="6">
<CInput label="Người tạo" :value="document.creator.name" readonly class="mb-0" />
</CCol>
</CRow>
<CTextarea
label="Trích yếu"
placeholder="Content..."
rows="5"
:value.sync="document.abstract"
/>
<CRow class="form-group">
<CCol sm="6">
<CFormGroup class="form-group mb-0">
<template #label>
<slot name="label">
<label>Nơi ban hành</label>
</slot>
</template>
<template #input>
<treeselect
v-model="document.publisher_id"
:multiple="false"
:options="publishers"
:clearable="false"
></treeselect>
</template>
</CFormGroup>
</CCol>
<CCol sm="6">
<CInput
:label="isIncome ? 'Ngày nhận' : 'Ngày ban hành'"
type="date"
:value.sync="document.effective_at"
class="mb-0"
/>
</CCol>
</CRow>
<CRow class="form-group">
<CCol sm="6">
<CFormGroup class="form-group mb-0">
<template #label>
<slot name="label">
<label>Người </label>
</slot>
</template>
<template #input>
<treeselect
v-model="document.signer_id"
:multiple="false"
:options="signers"
:clearable="false"
>
<label
slot="option-label"
slot-scope="{ node }"
>{{ node.raw.description ? node.raw.label + ' - ' + node.raw.description : node.raw.label }}</label>
</treeselect>
</template>
</CFormGroup>
</CCol>
<CCol sm="6">
<CInput label="Ngày ký" type="date" :value.sync="document.sign_at" class="mb-0" />
</CCol>
</CRow>
</CForm>
</CCardBody>
<CCardFooter>
<CButton
v-if="documentId"
size="sm"
@click="updateDocument"
class="float-right"
color="success"
>
<CIcon name="cil-check" />Lưu
</CButton>
<CButton v-else size="sm" @click="createDocument" class="float-right" color="success">
<CIcon name="cil-plus" />Tạo
</CButton>
</CCardFooter>
</CCard>
</template>
<script>
import services from "../../services/factory";
// import the component
import { Treeselect } from "@riophae/vue-treeselect";
// import the styles
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Detail",
props: {
documentId: {
required: false
}
},
components: { Treeselect },
data() {
return {
books: [],
types: [],
signers: [],
writers: [],
publishers: [],
document: {
book_id: null,
type_id: null,
symbol: null,
writer_id: null,
abstract: null,
publisher_id: null,
effective_at: null,
signer_id: null,
sign_at: null,
creator: {
id: this.$store.state.auth.currentUser.id,
name: this.$store.state.auth.currentUser.name
},
writer: {}
}
};
},
watch: {
documentId: {
handler() {
this.init();
}
},
$route: {
immediate: true,
handler(route) {
if (route.query && route.query.book) {
this.document.book_id = Number.parseInt(route.query.book);
}
}
}
},
computed: {
isIncome() {
return this.document.book_id == 1;
}
},
created() {
this.init();
},
methods: {
init() {
!this.documentId || this.fetchDocument();
this.fetchTypes();
this.fetchBooks();
this.fetchPublishers();
this.fetchSigners();
this.fetchWriters();
},
async fetchDocument() {
const documentResponse = await services.document.get(this.documentId, {
with: "creator;writer"
});
this.document = documentResponse.data;
return documentResponse.data;
},
async fetchTypes() {
const typeResponse = await services.documentType.all();
this.types = this.formatKeys(typeResponse.data, {
id: "id",
name: "label"
});
return typeResponse;
},
async fetchBooks() {
const bookResponse = await services.book.all();
this.books = this.formatKeys(bookResponse.data);
return bookResponse;
},
async fetchPublishers() {
const publisherResponse = await services.publisher.all();
this.publishers = this.formatKeys(publisherResponse.data, {
id: "id",
name: "label"
});
return publisherResponse;
},
async fetchSigners() {
const signerResponse = await services.signer.all();
this.signers = this.formatKeys(signerResponse.data, {
id: "id",
name: "label"
});
return signerResponse;
},
async fetchWriters(query = "") {
const writersResponse = await services.user.all({
search: `name:${query}`,
with: "department"
});
this.writers = this.formatKeys(writersResponse.data.data, {
id: "id",
name: "label"
});
if (
!this.writers.map(item => item.id).includes(this.document.writer_id) &&
this.document.writer
) {
this.writers.push({
id: this.document.writer.id,
label: this.document.writer.name
});
}
return writersResponse;
},
async updateDocument() {
await services.document
.update(this.document, this.documentId)
.then(response => {
this.$toast.success("Đã lưu");
this.$emit("update", response.data);
})
.catch(error => {
this.toastHttpError(error);
});
},
createDocument() {
services.document
.create(this.document)
.then(response => {
this.$router.push({ path: `/documents/${response.data.id}` });
this.$toast.success("Đã tạo văn bản");
})
.catch(error => {
this.toastHttpError(error);
});
},
onClearWriter(value) {
if (value != undefined) return;
this.document.writer_id = null;
}
}
};
</script>

View File

@@ -0,0 +1,150 @@
<template>
<CCard>
<CCardHeader>
<strong>Người nhận</strong>
<CBadge
color="success"
class="float-right"
v-c-tooltip="{ content: 'Đã xem' }"
>{{ seenReceivers.length }}</CBadge
>
</CCardHeader>
<CCardBody>
<CRow class="form-group">
<CCol sm="12">
<!-- <label>Chọn xem</label> -->
<treeselect
@select="addViewer"
@deselect="removeViewer"
:normalizer="normalizer"
v-model="viewers"
:multiple="true"
:options="viewerOptions"
:clearable="false"
>
<div
slot="value-label"
slot-scope="{ node }"
:class="seenStyle(node.raw.id)"
>{{ node.raw.name }}</div>
</treeselect>
</CCol>
</CRow>
</CCardBody>
</CCard>
</template>
<script>
import services from "../../services/factory";
// import the component
import Treeselect from "@riophae/vue-treeselect";
// import the styles
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Receivers",
props: {
documentId: {
required: true,
},
},
components: { Treeselect },
data() {
return {
// define the default value
viewers: [],
// define options
viewerOptions: [],
seenReceivers: [],
};
},
watch: {
documentId: {
handler() {
this.init();
},
},
},
created() {
this.init();
},
methods: {
async init() {
this.fetchDepartments(), this.fetchViewers();
},
async fetchDepartments() {
const departmentResponse = await services.department.all({
with: "users",
});
const departments = departmentResponse.data;
this.viewerOptions = departments;
return departments;
},
async fetchViewers() {
const receivers = await this.fetchReceivers();
this.viewers = receivers.map((receiver) => receiver.id);
this.seenReceivers = receivers
.filter((receiver) => receiver.pivot.seen)
.map((receiver) => receiver.id);
return receivers;
},
async fetchReceivers() {
const receiversResponse = await services.document.get(this.documentId, {
with: "receivers",
});
const receivers = receiversResponse.data.receivers;
this.handlerOptions = receivers;
return receivers;
},
removeViewer(item) {
var viewerIds = [item.id];
if(item.users){
viewerIds = item.users.map(u => u.id)
}
services.document
.unassignReceivers(this.documentId, viewerIds)
.then((response) => {
this.fetchReceivers();
})
.catch((error) => {
this.toastHttpError(error);
});
},
addViewer(item) {
var viewerIds = [item.id];
if(item.users){
viewerIds = item.users.map(u => u.id)
}
services.document
.assignReceivers(this.documentId, viewerIds)
.then((response) => {
this.fetchReceivers();
})
.catch((error) => {
this.toastHttpError(error);
});
},
seenStyle(userId) {
return this.seenReceivers.includes(userId) ? "seen" : null;
},
normalizer(node) {
return {
id: node.id,
label: node.name,
[node.users == undefined
? ""
: node.users.length > 0
? "children"
: ""]: node.users,
};
},
},
};
</script>
<style scoped>
.seen {
color: #2eb85c;
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<CCard>
<CCardHeader>
<strong>Nơi nhận</strong>
</CCardHeader>
<CCardBody>
<CRow class="form-group">
<CCol sm="12">
<treeselect
v-model="organizes"
:multiple="true"
:options="organizeOptions"
:clearable="false"
@select="addRecipient"
@deselect="removeRecipient"
></treeselect>
</CCol>
</CRow>
</CCardBody>
</CCard>
</template>
<script>
import services from "../../services/factory";
// import the component
import Treeselect from "@riophae/vue-treeselect";
// import the styles
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Recipients",
props: {
documentId: {
required: true
}
},
components: { Treeselect },
data() {
return {
// define the default value
organizes: [],
// define options
organizeOptions: []
};
},
watch: {
documentId: {
handler() {
this.init();
}
}
},
created() {
this.init();
},
methods: {
async init() {
this.fetchOrganizes();
this.fetchRecipients();
},
async fetchOrganizes() {
const organizesResponse = await services.publisher.all();
const organizes = this.formatKeys(organizesResponse.data, {
id: "id",
name: "label"
});
this.organizeOptions = organizes;
return organizes;
},
async fetchRecipients() {
const recipientResponse = await services.document.get(this.documentId , {
with: 'organizes'
});
this.organizes = recipientResponse.data.organizes.map(organize => organize.id);
return recipientResponse;
},
addRecipient(item) {
services.document
.assignRecipients(this.documentId, [item.id])
.catch(error => {
this.toastHttpError(error);
});
},
removeRecipient(item) {
services.document
.unassignRecipients(this.documentId, [item.id])
.catch(error => {
this.toastHttpError(error);
});
}
}
};
</script>

View File

@@ -0,0 +1,179 @@
<template>
<div>
<CCard>
<CCardHeader>
<CIcon name="cil-grid" />
{{title}}
<CButton
v-if="canCreate"
size="sm"
class="float-right"
color="primary"
variant="outline"
v-c-tooltip="'Tạo mới'"
@click="showCreate"
>
<CIcon name="cil-plus" />
</CButton>
</CCardHeader>
<CCardBody class="p-0">
<CDataTable
hover
:loading="loading"
:items="items"
:fields="fields"
@row-clicked="showDetail"
clickable-rows
sorter
column-filter
></CDataTable>
</CCardBody>
</CCard>
<CModal :title="title" :show.sync="isShowDetail">
<CInput
v-for="field in fields"
:label="field.label"
:key="field.key"
:value.sync="itemUpdating[field.key]"
/>
<slot v-if="!createMode" name="append-body"></slot>
<template #footer>
<CButton
v-if="canCreate && createMode"
size="sm"
class="float-right"
color="success"
@click="onClickCreate"
>
<CIcon name="cil-plus" /> Tạo mới
</CButton>
<CButton
v-if="canUpdate && !createMode"
size="sm"
class="float-right"
color="success"
@click="onClickSave"
>
<CIcon name="cil-check" /> Lưu
</CButton>
<CButton
v-if="canDelete && !createMode"
size="sm"
class="float-right"
color="danger"
@click="onClickDelete"
>
<CIcon name="cil-x" /> Xóa
</CButton>
</template>
</CModal>
</div>
</template>
<script>
export default {
name: "List",
props: {
service: {
required: true,
type: Object
},
title: {
required: false,
type: String,
default: "Danh sách"
},
canCreate: {
required: false,
type: Boolean,
default: true
},
canUpdate: {
required: false,
type: Boolean,
default: true
},
canDelete: {
required: false,
type: Boolean,
default: true
},
fields: {
required: true,
type: Array
}
},
data() {
return {
loading: false,
items: [],
itemSelected: {},
itemUpdating: {},
isShowDetail: false,
createMode: false
};
},
created() {
this.init();
},
methods: {
init() {
this.fetchList();
},
async fetchList() {
this.loading = true;
const response = await this.service.all();
this.items = response.data;
this.loading = false;
},
showDetail(item) {
this.$emit("show", item);
this.createMode = false;
this.itemSelected = item;
this.itemUpdating = _.clone(item);
this.isShowDetail = true;
},
onClickSave() {
this.service
.update(this.itemUpdating, this.itemSelected.id)
.then(response => {
this.isShowDetail = false;
this.$toast.success("Đã lưu");
this.fetchList();
})
.catch(error => {
this.toastHttpError(error);
});
},
showCreate() {
this.createMode = true;
this.itemUpdating = {};
this.isShowDetail = true;
},
onClickCreate() {
this.service
.create(this.itemUpdating)
.then(response => {
this.isShowDetail = false;
this.$toast.success("Đã tạo");
this.fetchList();
})
.catch(error => {
this.toastHttpError(error);
});
},
onClickDelete() {
this.service
.delete(this.itemSelected.id)
.then(response => {
this.isShowDetail = false;
this.$toast.success("Đã xóa");
this.fetchList();
})
.catch(error => {
this.toastHttpError(error);
});
}
}
};
</script>

View File

@@ -0,0 +1,117 @@
<template>
<CCard>
<CCardHeader>
<strong>Thông tin</strong>
</CCardHeader>
<CCardBody>
<CForm>
<CInput label="Mã" :value.sync="user.id" horizontal :readonly="true" />
<CInput
placeholder="Let us know your full name."
label="Tên"
:value.sync="user.name"
horizontal
/>
<CInput
label="Email"
placeholder="Enter your email"
type="email"
:value.sync="user.email"
horizontal
autocomplete="email"
/>
<CInput
label="Số điện thoại"
placeholder="Enter your tel"
:value.sync="user.tel"
horizontal
autocomplete="tel"
/>
<CInput label="Ngày sinh" type="date" :value.sync="user.birthday" horizontal />
<CSelect
label="Chức danh"
horizontal
:value.sync="user.title_id"
:options="titles"
placeholder="Please select"
/>
<CSelect
label="Phòng ban"
horizontal
:value.sync="user.department_id"
:options="departments"
placeholder="Please select"
/>
<CFormGroup class="form-group form-row">
<template #label>
<slot name="label">
<label class="col-form-label col-sm-3">Kích hoạt</label>
</slot>
</template>
<template #input>
<CSwitch class="mx-1" color="success" :checked.sync="user.active" />
</template>
</CFormGroup>
</CForm>
</CCardBody>
<CCardFooter>
<CButton type="submit" size="sm" @click="updateInfo" class="float-right" color="success">
<CIcon name="cil-check" />Lưu
</CButton>
</CCardFooter>
</CCard>
</template>
<script>
import services from "../../services/factory";
export default {
name: "Info",
props: {
userId: {
required: true,
},
isMe: {
required: false,
type: Boolean,
default: false,
},
},
beforeRouteEnter(to, from, next) {
next((vm) => {
vm.usersOpened = from.fullPath.includes("users");
});
},
data() {
return {
usersOpened: null,
user: [],
titles: [],
departments: [],
};
},
created() {
this.fetch();
},
methods: {
async fetch() {
const titleResponse = await services.title.all();
this.titles = this.formatKeys(titleResponse.data);
const departmentResponse = await services.department.all();
this.departments = this.formatKeys(departmentResponse.data);
const userResponse = await services.user.get(this.userId);
this.user = userResponse.data;
},
async updateInfo() {
return await (this.isMe ? services.auth : services.user)
.update(this.user, this.userId)
.then((response) => {
this.$toast.success("Đã lưu");
})
.catch((error) => {
this.toastHttpError(error);
});
},
},
};
</script>

View File

@@ -0,0 +1,75 @@
<template>
<CCard>
<CCardHeader>
<strong>Thay đổi mật khẩu</strong>
</CCardHeader>
<CCardBody>
<CForm>
<CInput placeholder="Nhập mật khẩu." label="Mật khẩu" type="password" horizontal />
<CInput
placeholder="Nhập mật khẩu mới."
label="Mật khẩu mới"
type="password"
:value.sync="password.password"
horizontal
/>
<CInput
placeholder="Nhập lại mật khẩu mới."
label="Xác nhận"
type="password"
:value.sync="password.password_confirmation"
horizontal
/>
</CForm>
</CCardBody>
<CCardFooter>
<CButton
type="submit"
@click="updatePassword"
size="sm"
class="float-right"
color="success"
>
<CIcon name="cil-check" />Thay đổi
</CButton>
</CCardFooter>
</CCard>
</template>
<script>
import services from "../../services/factory";
export default {
name: "Password",
props: {
userId: {
required: true,
},
isMe: {
required: false,
type: Boolean,
default: false,
},
},
data() {
return {
password: {
password_confirmation: "",
password: "",
},
};
},
methods: {
async updatePassword() {
return await (this.isMe ? services.auth : services.user)
.update(this.password, this.userId)
.then((response) => {
this.$toast.success("Đã thay đổi");
})
.catch((error) => {
this.toastHttpError(error);
});
},
},
};
</script>

View File

@@ -0,0 +1,65 @@
<template>
<CCard>
<CCardHeader>
<strong>Phân quyền</strong>
</CCardHeader>
<CCardBody>
<CForm>
<CRow form class="form-group" key="permission">
<CCol sm="12">
<CInputCheckbox
v-for="(permission) in permissions"
:key="permission.id"
:label="permission.name"
:value="permission.id"
:checked="user.permissions.map(permission => permission.id).includes(permission.id)"
@update:checked="givePermission"
/>
</CCol>
</CRow>
</CForm>
</CCardBody>
</CCard>
</template>
<script>
import services from "../../services/factory";
export default {
name: "Permission",
props: {
userId: {
required: true
}
},
data() {
return {
permissions: [],
user: []
};
},
created() {
this.fetch();
},
methods: {
async fetch() {
const userResponse = await services.user.get(this.userId, {
with: "permissions"
});
this.user = userResponse.data;
const permissionResponse = await services.permission.all();
this.permissions = permissionResponse.data;
},
async givePermission(checked, event) {
const permissionId = event.target.value;
const permissionRequest = (await checked)
? services.user.givePermission(permissionId, this.userId)
: services.user.revokePermission(permissionId, this.userId);
permissionRequest.catch(error => {
this.toastHttpError(error);
});
return permissionRequest;
}
}
};
</script>

View File

@@ -0,0 +1,65 @@
<template>
<CCard>
<CCardHeader>
<strong>Phân nhóm</strong>
</CCardHeader>
<CCardBody>
<CForm>
<CRow form class="form-group" key="role">
<CCol sm="12">
<CInputCheckbox
v-for="(role) in roles"
:key="role.id"
:label="role.name"
:value="role.id"
:checked="user.roles.map(role => role.id).includes(role.id)"
@update:checked="giveRole"
/>
</CCol>
</CRow>
</CForm>
</CCardBody>
</CCard>
</template>
<script>
import services from "../../services/factory";
export default {
name: "Role",
props: {
userId: {
required: true
}
},
data() {
return {
roles: [],
user: []
};
},
created() {
this.fetch();
},
methods: {
async fetch() {
const userResponse = await services.user.get(this.userId, {
with: "roles"
});
this.user = userResponse.data;
const roleResponse = await services.role.all();
this.roles = roleResponse.data;
},
async giveRole(checked, event) {
const roleId = event.target.value;
const roleRequest = (await checked)
? services.user.giveRole(roleId, this.userId)
: services.user.revokeRole(roleId, this.userId);
roleRequest.catch(error => {
this.toastHttpError(error);
});
return roleRequest;
}
}
};
</script>

View File

@@ -0,0 +1,74 @@
<template>
<CRow class="form-group">
<CCol sm="12">
<label>Phân theo chức năng</label>
<treeselect
@select="addPermission"
@deselect="removePermission"
v-model="permissions"
:multiple="true"
:options="permissionOptions"
:clearable="false"
></treeselect>
</CCol>
</CRow>
</template>
<script>
import services from "../../services/factory";
// import the component
import Treeselect from "@riophae/vue-treeselect";
// import the styles
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "TreePermission",
props: {
userId: {
required: true
}
},
components: { Treeselect },
data() {
return {
permissionOptions: [],
permissions: []
};
},
created() {
this.fetchPermissions();
this.fetchCurrentPermissions();
},
methods: {
async fetchPermissions() {
const permissionResponse = await services.permission.all();
this.permissionOptions = this.formatKeys(permissionResponse.data, {
id: "id",
name: "label"
});
return permissionResponse;
},
async fetchCurrentPermissions() {
const userResponse = await services.user.get(this.userId, {
with: "permissions"
});
this.permissions = userResponse.data.permissions.map(
permission => permission.id
);
},
addPermission(permission) {
services.user.givePermission(permission.id, this.userId).catch(error => {
this.toastHttpError(error);
});
},
removePermission(permission) {
services.user
.revokePermission(permission.id, this.userId)
.catch(error => {
this.toastHttpError(error);
});
}
}
};
</script>

View File

@@ -0,0 +1,70 @@
<template>
<CRow class="form-group">
<CCol sm="12">
<label>Phân theo nhóm</label>
<treeselect
@select="addRole"
@deselect="removeRole"
v-model="roles"
:multiple="true"
:options="roleOptions"
:clearable="false"
></treeselect>
</CCol>
</CRow>
</template>
<script>
import services from "../../services/factory";
// import the component
import Treeselect from "@riophae/vue-treeselect";
// import the styles
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "TreeRole",
props: {
userId: {
required: true
}
},
components: { Treeselect },
data() {
return {
roleOptions: [],
roles: []
};
},
created() {
this.fetchRoles();
this.fetchCurrentRoles();
},
methods: {
async fetchRoles() {
const roleResponse = await services.role.all();
this.roleOptions = this.formatKeys(roleResponse.data, {
id: "id",
name: "label"
});
return roleResponse;
},
async fetchCurrentRoles() {
const userResponse = await services.user.get(this.userId, {
with: "roles"
});
this.roles = userResponse.data.roles.map(role => role.id);
},
addRole(role) {
services.user.giveRole(role.id, this.userId).catch(error => {
this.toastHttpError(error);
});
},
removeRole(role) {
services.user.revokeRole(role.id, this.userId).catch(error => {
this.toastHttpError(error);
});
}
}
};
</script>