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,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>