This commit is contained in:
Viet An
2026-05-12 15:13:43 +07:00
parent f1ecd5c7ff
commit 336c8c9036
25 changed files with 1195 additions and 852 deletions

View File

@@ -192,7 +192,7 @@
>
<span class="icon">
<Icon
name="material-symbols:add-2-rounded"
name="material-symbols:add-rounded"
:size="22"
/>
</span>
@@ -210,7 +210,7 @@
>
<span class="icon">
<Icon
name="material-symbols:settings-outline-rounded"
name="material-symbols:table-edit-outline"
:size="22"
/>
</span>
@@ -240,8 +240,8 @@
>
</p>
</div>
<div class="tabs is-toggle">
<ul class="is-flex-grow-0 mx-auto">
<div class="tabs is-toggle is-centered">
<ul>
<li
v-for="(v, i) in getMenu().filter((x) =>
currentField.format === 'number'
@@ -254,32 +254,31 @@
:class="selectTab.code === v.code ? 'is-active' : 'has-text-primary'"
@click="changeTab(v)"
>
<a class="px-4 py-1.5">{{ v.name }}</a>
<a class="px-8 py-1.5 fs-14">{{ v.name }}</a>
</li>
</ul>
</div>
<div v-if="currentTab === 'detail'">
<p class="fs-14 mt-3">
<strong> Tên trường: </strong> {{ currentField.name }}
<a @click="copyContent(currentField.name)">
<span class="tooltip">
<SvgIcon
class="ml-1"
v-bind="{ name: 'copy.svg', type: 'primary', size: 16 }"
></SvgIcon>
<span
class="tooltiptext"
style="top: 110%; bottom: unset; min-width: max-content; left: 25px"
>Copy</span
>
<div class="fs-14 mt-3 is-flex is-gap-0.5 is-align-items-center">
<p class="font-semibold">Tên trường:</p>
<p>{{ currentField.name }}</p>
<button
class="button is-white is-small"
@click="copyContent(currentField.name)"
>
<span class="icon">
<Icon
name="material-symbols:content-copy-outline-rounded"
:size="16"
/>
</span>
</a>
</p>
</button>
</div>
<label class="label fs-14 mt-3">Mô tả<span class="has-text-danger"> *</span></label>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input fs-14"
class="input fs-14 h-full"
type="text"
@change="changeLabel($event.target.value)"
v-model="label"
@@ -287,39 +286,48 @@
</div>
<div class="control">
<button
class="button"
class="button h-full"
@click="editLabel()"
>
<SvgIcon v-bind="{ name: 'pen.svg', type: 'dark', size: 19 }"></SvgIcon>
<span class="icon">
<Icon
name="material-symbols:edit-outline-rounded"
:size="18"
/>
</span>
</button>
</div>
</div>
<p
class="help is-danger"
v-if="errors.find((v) => v.name === 'label')"
class="help is-danger"
>
{{ errors.find((v) => v.name === "label").msg }}
</p>
<div class="field mt-3">
<label class="label fs-14">Kiểu dữ liệu<span class="has-text-danger"> * </span></label>
<div class="control fs-14">
<span
class="mr-4"
v-for="(v, i) in datatype"
<button
v-for="v in datatype"
class="button is-white fs-14"
>
<span
class="icon-text"
v-if="radioType === v"
>
<SvgIcon v-bind="{ name: 'radio-checked.svg', type: 'gray', size: 22 }"></SvgIcon>
<span class="icon">
<Icon
:name="
radioType === v
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
{{ v.name }}
</span>
<span>{{ v.name }}</span>
</button>
</div>
</div>
<div
class="field is-horizontal"
v-if="currentField.format === 'number'"
class="field is-horizontal"
>
<div class="field-body">
<div class="field">
@@ -334,7 +342,7 @@
position: 'is-top-left',
}"
@option="selected('_account', $event)"
></SearchBox>
/>
</div>
<p
class="help has-text-danger"
@@ -361,27 +369,25 @@
<div class="field-body">
<div class="field">
<label class="label fs-14">Định dạng nâng cao</label>
<p class="control fs-14">
<span
class="mr-4"
v-for="(v, i) in colorchoice.filter((v) => v.code !== 'condition')"
<div class="control">
<button
v-for="v in colorchoice.filter((v) => v.code !== 'condition')"
@click="changeTemplate(v)"
class="button is-white fs-14"
>
<a
class="icon-text"
@click="changeTemplate(v)"
>
<SvgIcon
v-bind="{
name: `radio-${radioTemplate === v.code ? '' : 'un'}checked.svg`,
type: 'gray',
size: 22,
}"
>
</SvgIcon>
</a>
{{ v.name }}
</span>
</p>
<span class="icon">
<Icon
:name="
radioTemplate === v.code
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
<span>{{ v.name }}</span>
</button>
</div>
</div>
</div>
</div>
@@ -390,10 +396,10 @@
v-if="radioTemplate === 'option'"
>
<button
class="button is-primary is-small has-text-white"
class="button is-primary is-light fs-14"
@click="showSidebar()"
>
<span class="fs-14">{{ `${currentField.template ? "Sửa" : "Tạo"} định dạng` }}</span>
{{ `${currentField.template ? "Sửa" : "Tạo"} định dạng` }}
</button>
</p>
</div>
@@ -414,7 +420,7 @@
@label="changeLabel"
@updatefields="updateFields"
@close="close"
></Modal>
/>
</template>
<script setup>
import { useStore } from "@/stores/index";
@@ -522,7 +528,6 @@ function doRemove() {
copy.fields.splice(idx, 1);
copy.update = { fields: copy.fields };
store.commit(props.pagename, copy);
emit("close");
}
function fieldList() {
showmodal.value = {
@@ -538,7 +543,7 @@ function tableOption() {
component: "datatable/TableSetting",
vbind: { pagename: props.pagename },
title: "Tùy chọn bảng",
width: "40%",
width: "50%",
height: "400px",
};
}
@@ -556,9 +561,10 @@ const changeLabel = function (text) {
function editLabel() {
showmodal.value = {
component: "datatable/EditLabel",
title: "Điều chỉnh tiêu đề",
width: "500px",
height: "300px",
vbind: { label: label },
vbind: { label },
};
}
const changeTemplate = function (v) {
@@ -598,7 +604,7 @@ const saveSetting = function () {
vbind: { pagename: props.pagename, classify: 4 },
title: "Lưu thiết lập",
width: "500px",
height: "400px",
height: "auto",
};
};
const showSidebar = function () {

View File

@@ -4,33 +4,41 @@
<div class="field-body">
<div class="field">
<label class="label">Đối tượng</label>
<p class="control fs-14">
<b-radio
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in types"
:key="i"
v-model="type"
:native-value="v"
@input="changeType(v)"
class="radio"
>
<input
v-model="type"
@input="changeType(v)"
type="radio"
name="type"
/>
{{ v.name }}
</b-radio>
</p>
</label>
</div>
</div>
<div class="field">
<label class="label">Kích cỡ</label>
<p class="control fs-14">
<b-radio
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in sizes.filter((v) =>
type ? (type.code === 'tag' ? v.code !== 'is-small' : 1 > 0) : true,
)"
:key="i"
v-model="size"
:native-value="v"
@input="changeType(v)"
class="radio"
>
<input
v-model="size"
@input="changeType(v)"
type="radio"
name="size"
/>
{{ v.name }}
</b-radio>
</p>
</label>
</div>
</div>
<div class="field">
<label
@@ -38,20 +46,21 @@
v-if="['tag'].find((v) => v === type.code)"
>Hình khối</label
>
<p
class="control fs-14"
v-if="['tag'].find((v) => v === type.code)"
>
<b-radio
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in shapes"
:key="i"
v-model="shape"
:native-value="v"
@input="changeType(v)"
class="radio"
>
<input
v-model="shape"
@input="changeType(v)"
type="radio"
name="shape"
/>
{{ v.name }}
</b-radio>
</p>
</label>
</div>
</div>
</div>
</div>
@@ -116,7 +125,7 @@
:key="i"
@click="changeTab(v)"
>
<a class="fs-15">{{ v.name }}</a>
<a class="">{{ v.name }}</a>
</li>
</ul>
</div>
@@ -346,68 +355,77 @@
</div>
</template>
<template v-else-if="tab.code === 'template'">
<p class="mb-3">
<a
<div class="buttons mb-3">
<button
@click="copyContent()"
class="mr-6"
class="button is-primary is-light fs-14"
>
<span class="icon-text">
<SvgIcon
class="mr-2"
v-bind="{ name: 'copy.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Copy</span>
<span class="icon">
<Icon
name="material-symbols:content-copy-outline-rounded"
:size="18"
/>
</span>
</a>
<a
<span>Copy</span>
</button>
<button
@click="paste()"
class="mr-6"
class="button is-primary is-light fs-14"
>
<span class="icon-text">
<SvgIcon
class="mr-2"
v-bind="{ name: 'pen1.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Paste</span>
<span class="icon">
<Icon
name="material-symbols:content-paste-rounded"
:size="18"
/>
</span>
</a>
</p>
<span>Paste</span>
</button>
</div>
<div>
<textarea
class="textarea fs-14"
rows="8"
class="textarea fs-13"
style="font-family: monospace"
rows="4"
v-model="text"
@dblclick="doCheck"
></textarea>
</div>
<p class="mt-5">
<span class="icon-text fsb-18">
Replace
<SvgIcon v-bind="{ name: 'right.svg', type: 'dark', size: 22 }"></SvgIcon>
<span class="icon-text">
<span class="fs-17 font-semibold">Replace</span>
<span class="icon">
<Icon
name="material-symbols:arrow-forward-ios-rounded"
:size="18"
/>
</span>
</span>
</p>
<div class="field is-grouped mt-4">
<div class="control">
<p class="fsb-14 mb-1">Đoạn text</p>
<label class="label">Đoạn text</label>
<input
class="input"
type="text"
placeholder=""
v-model="source"
/>
</div>
<div class="control">
<p class="fsb-14 mb-1">Thay bằng</p>
<label class="label">Thay bằng</label>
<input
class="input"
type="text"
placeholder=""
v-model="target"
/>
</div>
<div class="control pl-5">
<div class="control">
<label
class="label"
style="opacity: 0"
>Hidden</label
>
<button
class="button is-primary is-outlined mt-5"
class="button is-primary is-light"
@click="replace()"
>
Replace

View File

@@ -1,20 +1,26 @@
<template>
<div
class="field is-grouped is-grouped-multiline pl-2"
class="field is-grouped is-grouped-multiline is-gap-4 px-2"
v-if="filters ? filters.length > 0 : false"
>
<div class="control mr-5">
<div class="control">
<a
class="button is-primary is-small has-text-white has-text-weight-bold"
class="button is-primary is-light"
@click="updateData({ filters: [] })"
>
<span class="fs-14">Xóa lọc</span>
</a>
</div>
<div class="control pr-2 mr-5">
<div class="control">
<span class="icon-text">
<SvgIcon v-bind="{ name: 'sigma.svg', type: 'primary', size: 20 }"></SvgIcon>
<span class="fsb-18 has-text-primary">{{ totalRows }}</span>
<span class="icon">
<Icon
name="mdi:sigma"
:size="20"
class="has-text-primary"
/>
</span>
<span class="fs-16 font-semibold has-text-primary">{{ totalRows }}</span>
</span>
</div>
<div
@@ -24,16 +30,16 @@
>
<div class="tags has-addons is-marginless">
<a
class="tag is-primary has-text-white is-marginless"
class="tag is-primary is-light is-marginless"
@click="showCondition(v)"
>{{ v.label.indexOf(">") >= 0 ? $stripHtml(v.label, 30) : v.label }}</a
>
<a
class="tag is-delete is-marginless has-text-black-bis"
class="tag is-delete is-marginless"
@click="removeFilter(i)"
></a>
</div>
<span class="help has-text-black-bis">
<span class="help">
{{
v.sort
? v.sort
@@ -59,14 +65,13 @@
v-for="(field, i) in displayFields"
:key="i"
:style="field.headerStyle"
class="is-clickable header"
@click="showField(field)"
>
<div
@click="showField(field)"
:style="field.dropStyle"
>
<div :style="field.dropStyle">
<a
v-if="field.label.indexOf('<') < 0"
class="has-text-white font-semibold"
class="font-semibold"
>{{ field.label }}</a
>
<a v-else>
@@ -160,13 +165,12 @@ var currentPage = 1;
var displayFields = ref([]);
var displayData = [];
var pagedata = store[props.pagename];
console.log("props.pagename", props.pagename);
console.log("store", toRaw(store));
console.log("store[props.pagename]", toRaw(store[props.pagename]));
console.log("pagedata.tablesetting", toRaw(pagedata.tablesetting)); // table-font-size is 13
console.log("store.tablesetting", toRaw(store.tablesetting)); // table-font-size is 12
// var tablesetting = $copy(pagedata.tablesetting || store.tablesetting);
var tablesetting = $copy(store.tablesetting);
var tablesetting = $copy(pagedata.tablesetting || store.tablesetting);
if (!Array.isArray(tablesetting)) {
tablesetting = Object.values(tablesetting);
}
// var tablesetting = $copy(store.tablesetting);
// const tablesettingObj = Object.fromEntries(tablesetting.map((v) => [v.code, v]));
var perPage = Number($find(tablesetting, { code: "per-page" }, "detail")) || 20;
var filters = $copy(pagedata.filters || []);
var currentField;
@@ -551,7 +555,10 @@ setTimeout(() => updateShow(), 200);
</script>
<style scoped>
:deep(.table tbody tr:hover td, .table tbody tr:hover th) {
background-color: hsl(0, 0%, 78%);
color: rgb(0, 0, 0);
--bulma-table-row-hover-background-color: var(--bulma-scheme-main-ter);
background-color: var(--bulma-table-row-hover-background-color);
}
.header:hover a {
opacity: 0.75;
}
</style>

View File

@@ -1,12 +1,11 @@
<template>
<div>
<p class="fsb-20 mb-5">Điều chỉnh tiêu đề</p>
<div
v-for="(v, i) in arr"
:key="i"
:class="i > 0 ? 'mt-4' : null"
:class="i > 0 && 'mt-4'"
>
<p class="fsb-14">Dòng thứ {{ i + 1 }}<span class="has-text-danger"> *</span></p>
<p class="font-semibold">Dòng thứ {{ i + 1 }}<span class="has-text-danger"> *</span></p>
<div class="field has-addons mt-1">
<div class="control is-expanded">
<input
@@ -16,23 +15,31 @@
/>
</div>
<div class="control">
<a
class="button px-2 is-primary"
<button
class="button is-success is-light"
@click="add()"
>
<span> <SvgIcon v-bind="{ name: 'add1.png', type: 'white', size: 17 }"></SvgIcon></span>
</a>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
</button>
</div>
<div
class="control"
@click="remove(i)"
v-if="i > 0"
>
<a class="button px-2 is-dark">
<span>
<SvgIcon v-bind="{ name: 'bin.svg', type: 'white', size: 17 }"></SvgIcon>
<button class="button is-danger is-light">
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="20"
/>
</span>
</a>
</button>
</div>
</div>
<p
@@ -50,10 +57,10 @@
Cập nhật
</button>
<button
class="button is-dark"
class="button is-white"
@click="$emit('close')"
>
Hủy bỏ
Hủy
</button>
</div>
</div>
@@ -72,7 +79,7 @@ export default {
if (!this.$empty(v)) {
let label = v + "</p>";
label = this.$stripHtml(label);
this.arr.push({ label: label });
this.arr.push({ label });
}
});
},

View File

@@ -27,45 +27,56 @@
/>
</div>
</div>
<div class="field is-narrow">
<div class="field is-narrow has-addons">
<p class="control">
<a @click="addAttr()">
<SvgIcon v-bind="{ name: 'add1.png', type: 'gray', size: 18 }"></SvgIcon>
</a>
<a
class="ml-2"
@click="remove(i)"
>
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'gray', size: 18 }"></SvgIcon>
</a>
<a
class="ml-2"
<button
class="button is-primary is-light"
@click="jsonData(v, i)"
>
<SvgIcon v-bind="{ name: 'apps.svg', type: 'gray', size: 18 }"></SvgIcon>
</a>
<span class="icon">
<Icon
name="material-symbols:app-registration-rounded"
:size="20"
/>
</span>
</button>
</p>
<p class="control">
<button
class="button is-primary is-light"
@click="remove(i)"
>
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="20"
/>
</span>
</button>
</p>
</div>
</div>
</div>
</template>
<div
class="mb-6"
v-else
<button
class="button is-success is-light"
@click="addAttr()"
>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
<span>Thêm thuộc tính</span>
</button>
<div class="mt-2">
<button
class="button is-primary has-text-white"
@click="addAttr()"
>
Thêm thuộc tính
</button>
</div>
<div class="buttons mt-5">
<a
class="button is-primary has-text-white"
class="button is-primary"
@click="update()"
>Cập nhật</a
>
Cập nhật
</button>
</div>
<Modal
@close="comp = undefined"

View File

@@ -1,55 +1,61 @@
<template>
<!-- v-if="currentsetting ? currentsetting.user === login.id : false" -->
<div
class="mb-4"
v-if="currentsetting ? currentsetting.user === login.id : false"
v-if="currentsetting"
>
<p class="fs-16 has-text-findata">
<p>
Đang mở: <b>{{ $stripHtml(currentsetting.name, 40) }}</b>
</p>
</div>
<div class="field">
<label class="label fs-14">Chọn chế độ lưu <span class="has-text-danger"> * </span></label>
<label class="label">Chọn chế độ lưu <span class="has-text-danger"> * </span></label>
<div class="control is-expanded fs-14">
<a
class="mr-5"
<button
class="button is-white has-text-inherit"
v-if="isOverwrite()"
@click="changeType('overwrite')"
>
<span class="icon-text">
<SvgIcon
v-bind="{
name: radioSave === 'overwrite' ? 'radio-checked.svg' : 'radio-unchecked.svg',
type: 'gray',
size: 22,
}"
></SvgIcon>
Ghi đè
<span class="icon">
<Icon
:name="
radioSave === 'overwrite'
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
</a>
<a @click="changeType('new')">
<span class="icon-text">
<SvgIcon
v-bind="{
name: radioSave === 'new' ? 'radio-checked.svg' : 'radio-unchecked.svg',
type: 'gray',
size: 22,
}"
></SvgIcon>
Tạo mới
<span>Ghi đè</span>
</button>
<button
class="button is-white"
@click="changeType('new')"
>
<span class="icon">
<Icon
:name="
radioSave === 'new'
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
</a>
<span>Tạo mới</span>
</button>
</div>
</div>
<template v-if="radioSave === 'new'">
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14">Tên thiết lập <span class="has-text-danger"> * </span></label>
<div class="field">
<label class="label">Tên thiết lập <span class="has-text-danger"> * </span></label>
<div class="control">
<!-- ref="name" -->
<input
class="input"
type="text"
placeholder=""
v-model="name"
ref="name"
v-on:keyup.enter="saveSetting"
/>
</div>
@@ -60,12 +66,12 @@
{{ errors.find((v) => v.name === "name").msg }}
</div>
</div>
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14"> Mô tả </label>
<div class="field">
<label class="label"> tả </label>
<p class="control is-expanded">
<textarea
class="textarea"
rows="4"
rows="3"
v-model="note"
></textarea>
</p>
@@ -93,11 +99,12 @@
{{ status ? "Lưu thiết lập thành công." : "Lỗi. Lưu thiết lập thất bại." }}
</label>
<p class="control is-expanded">
<a
class="button is-primary has-text-white"
<button
:class="['button is-primary', isLoading && 'is-loading']"
@click="saveSetting()"
>Lưu lại</a
>
Lưu lại
</button>
</p>
</div>
</template>
@@ -106,28 +113,30 @@ import { ref } from "vue";
import { useStore } from "@/stores/index";
const emit = defineEmits([]);
const store = useStore();
var props = defineProps({
const props = defineProps({
pagename: String,
classify: String,
option: String,
data: Object,
focus: Boolean,
});
const { $empty, $copy, $filter, $stripHtml, $updateapi, $insertapi, $findIndex, $snackbar } = useNuxtApp();
var pagename = props.pagename;
var radioOption = ref();
const { $empty, $copy, $stripHtml, $updateapi, $insertapi, $findIndex, $snackbar } = useNuxtApp();
const radioOption = ref();
var login = { id: 1 };
var errors = [];
var radioType = undefined;
var radioDefault = 0;
var radioSave = ref("new");
var note = undefined;
var status = undefined;
var name = undefined;
const errors = ref([]);
const radioType = ref();
const radioDefault = 0;
const radioSave = ref("overwrite");
const name = ref();
const note = ref();
const status = undefined;
var currentsetting = undefined;
var pagename = props.pagename;
var pagedata = store[props.pagename];
const isLoading = ref(false);
async function saveSetting() {
errors = [];
errors.value = [];
let detail = pagename ? { fields: pagedata.fields } : {};
if (pagename) {
let element = pagedata.tablesetting || {};
@@ -139,19 +148,21 @@ async function saveSetting() {
if (props.option) detail.option = props.option;
if (props.data) detail.data = props.data;
let data = {
user: login.id,
name: name,
detail: detail,
note: note,
type: radioType.id,
// user: login.id,
user: undefined,
name: name.value,
detail,
note,
type: radioType.value.id,
classify: props.classify ? props.classify : store.settingclass.find((v) => v.code === "data-field").id,
default: radioDefault,
update_time: new Date(),
};
let result;
isLoading.value = true;
if (radioSave.value === "new") {
if ($empty(name)) {
return errors.push({
if ($empty(name.value)) {
return errors.value.push({
name: "name",
msg: "Tên thiết lập không được bỏ trống",
});
@@ -163,6 +174,7 @@ async function saveSetting() {
copy.update_time = new Date();
result = await $updateapi("usersetting", copy);
}
isLoading.value = false;
if (radioSave.value === "new") {
emit("modalevent", { name: "opensetting", data: result });
} else {
@@ -187,11 +199,12 @@ function changeOption(v) {
radioOption.value = v.code;
}
function initData() {
radioType = store.settingtype.find((v) => v.code === "private");
radioType.value = store.settingtype.find((v) => v.code === "private");
if (props.pagename) currentsetting = $copy(pagedata.setting ? pagedata.setting : undefined);
if (!currentsetting) radioSave.value = "new";
// disable temp: for now, radioSave is always 'overwrite'
/* if (!currentsetting) radioSave.value = "new";
else if (currentsetting.user !== login.id) radioSave.value = "new";
else radioSave.value = "overwrite";
else radioSave.value = "overwrite"; */
}
initData();
</script>

View File

@@ -2,10 +2,12 @@
<div class="tabs is-boxed">
<ul>
<li
:class="selectType.code === v.code ? 'is-active fs-16' : 'fs-16'"
v-for="v in fieldType"
:class="selectType.code === v.code && 'is-active'"
>
<a @click="selectType = v"
<a
class="has-text-inherit"
@click="selectType = v"
><span>{{ v.name }}</span></a
>
</li>
@@ -148,7 +150,7 @@
</div>
</div>
</template>
<div class="field px-0 mx-0">
<div class="field">
<label class="label">Tên trường <span class="has-text-danger"> * </span> </label>
<p class="control">
<input
@@ -166,13 +168,13 @@
{{ errors.find((v) => v.name === "name").message }}
</p>
<p
class="help has-text-primary"
class="help has-text-grey"
v-else
>
Tên trường do hệ thống tự sinh.
</p>
</div>
<div class="mt-5">
<div class="field">
<label class="label"> tả<span class="has-text-danger"> *</span></label>
<div class="field has-addons">
<div class="control is-expanded">
@@ -187,7 +189,12 @@
class="button"
@click="editLabel()"
>
<span><SvgIcon v-bind="{ name: 'pen.svg', type: 'dark', size: 17 }"></SvgIcon></span>
<span class="icon">
<Icon
name="material-symbols:edit-outline-rounded"
:size="18"
/>
</span>
</button>
</div>
</div>
@@ -199,53 +206,47 @@
</p>
</div>
<div
class="field mt-5"
class="field"
v-if="selectType.code === 'empty'"
>
<label class="label"
>Kiểu dữ liệu
<span class="has-text-danger"> * </span>
</label>
<div class="control fs-14">
<span
class="mr-4"
<div class="control">
<button
v-for="(v, i) in datatype"
class="button is-white fs-14"
@click="changeType(v)"
>
<a
class="icon-text"
@click="changeType(v)"
>
<SvgIcon
v-bind="{
name: `radio-${radioType.code === v.code ? '' : 'un'}checked.svg`,
type: 'gray',
size: 22,
}"
></SvgIcon>
</a>
{{ v.name }}
</span>
<span class="icon">
<Icon
:name="
radioType.code === v.code
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
<span>{{ v.name }}</span>
</button>
</div>
</div>
<div class="field mt-5">
<p class="control">
<a
class="button is-primary has-text-white"
@click="selectType.code === 'formula' ? createField() : createEmptyField()"
>Tạo cột</a
>
</p>
</div>
<button
class="button is-primary"
@click="selectType.code === 'formula' ? createField() : createEmptyField()"
>
Tạo cột
</button>
<Modal
v-bind="showmodal"
v-if="showmodal"
@label="changeLabel"
@close="close"
></Modal>
/>
</template>
<script setup>
import { useStore } from "@/stores/index";
import ScrollBox from "~/components/datatable/ScrollBox";
const emit = defineEmits(["modalevent"]);
const store = useStore();
const { $id, $copy, $clone, $empty, $stripHtml, $createField, $calc, $isNumber } = useNuxtApp();
@@ -256,7 +257,7 @@ var props = defineProps({
filterData: Object,
width: String,
});
const moneyunit = store.moneyunit;
const moneyunit = store.moneyunit || [];
const datatype = store.datatype;
var showmodal = ref();
var pagedata = store[props.pagename];
@@ -265,16 +266,15 @@ var data = [];
var current = 1;
var filterData = [];
var loading = false;
var fieldType = [
const fieldType = [
{ code: "formula", name: "Tạo công thức" },
{ code: "empty", name: "Tạo cột rỗng" },
];
var errors = [];
var tags = [];
var formula = undefined;
var name = `f${$id().toLocaleLowerCase()}`;
var label = undefined;
var errors = [];
const errors = ref([]);
var selectType = fieldType.find((v) => v.code === "empty");
var radioType = ref(datatype.find((v) => v.code === "string"));
var fields = [];
@@ -362,25 +362,25 @@ function checkFunc() {
return error ? "error" : content;
}
function checkValid() {
errors = [];
errors.value = [];
if (tags.length === 0 && choice === "column") {
errors.push({
errors.value.push({
name: "tags",
message: "Chưa chọn trường xây dựng công thức.",
});
}
if (!$empty(formula) ? $empty(formula.trim()) : true) {
errors.push({ name: "formula", message: "Công thức không được bỏ trống." });
errors.value.push({ name: "formula", message: "Công thức không được bỏ trống." });
}
if (!$empty(label) ? $empty(label.trim()) : true)
errors.push({ name: "label", message: "Mô tả không được bỏ trống." });
errors.value.push({ name: "label", message: "Mô tả không được bỏ trống." });
else if (pagedata.fields.find((v) => v.label.toLowerCase() === label.toLowerCase())) {
errors.push({
errors.value.push({
name: "label",
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
if (errors.length > 0) return false;
if (errors.value.length > 0) return false;
//check formula in case use column
if (choice === "column") {
let val = $copy(formula);
@@ -391,20 +391,20 @@ function checkValid() {
try {
let value = $calc(val);
if (isNaN(value) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) {
errors.push({ name: "formula", message: "Công thức không hợp lệ" });
errors.value.push({ name: "formula", message: "Công thức không hợp lệ" });
}
} catch (err) {
console.log(err);
errors.push({ name: "formula", message: "Công thức không hợp lệ" });
errors.value.push({ name: "formula", message: "Công thức không hợp lệ" });
}
} else {
if (checkFunc() === "error")
errors.push({
errors.value.push({
name: "formula",
message: `Hàm ${func.toUpperCase()} không hợp lệ`,
});
}
return errors.length > 0 ? false : true;
return errors.value.length > 0 ? false : true;
}
function createField() {
if (!checkValid()) return;
@@ -429,23 +429,26 @@ function createField() {
emit("close");
}
function createEmptyField() {
errors = [];
if (!$empty(name) ? $empty(name.trim()) : true) errors.push({ name: "name", message: "Tên không được bỏ trống." });
console.log("createEmptyField");
errors.value = [];
if (!$empty(name) ? $empty(name.trim()) : true)
errors.value.push({ name: "name", message: "Tên không được bỏ trống." });
else if (pagedata.fields.find((v) => v.name.toLowerCase() === name.toLowerCase())) {
errors.push({
errors.value.push({
name: "name",
message: "Tên trường bị trùng. Hãy đặt tên khác.",
});
}
if (!$empty(label) ? $empty(label.trim()) : true)
errors.push({ name: "label", message: "Mô tả không được bỏ trống." });
errors.value.push({ name: "label", message: "Mô tả không được bỏ trống." });
else if (pagedata.fields.find((v) => v.label.toLowerCase() === label.toLowerCase())) {
errors.push({
errors.value.push({
name: "label",
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
if (errors.length > 0) return;
console.log("errors", errors);
if (errors.value.length > 0) return;
let field = $createField(name.trim(), label.trim(), radioType.value.code, true);
if (selectType.code === "chart") field = createChartField();
let copy = $clone(pagedata);
@@ -453,6 +456,7 @@ function createEmptyField() {
copy.update = { fields: copy.fields };
store.commit(props.pagename, copy);
//pagedata = copy
console.log("field", field);
emit("newfield", field);
label = undefined;
name = `f${$id()}`;

View File

@@ -1,11 +1,11 @@
<template>
<table class="table">
<table class="table is-fullwidth">
<thead>
<tr class="fs-14">
<th>#</th>
<th>Tên trường</th>
<th>Tên cột</th>
<th>...</th>
<th class="is-narrow">...</th>
</tr>
</thead>
<tbody>
@@ -23,21 +23,47 @@
</td>
<td>{{ $stripHtml(v.label, 50) }}</td>
<td>
<a
class="mr-4"
@click="moveDown(v, i)"
>
<SvgIcon v-bind="{ name: 'down1.png', type: 'dark', size: 18 }"></SvgIcon>
</a>
<a
class="mr-4"
@click="moveUp(v, i)"
>
<SvgIcon v-bind="{ name: 'up.png', type: 'dark', size: 18 }"></SvgIcon>
</a>
<a @click="askConfirm(v, i)">
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'dark', size: 18 }"></SvgIcon>
</a>
<div class="field has-addons">
<p class="control">
<button
class="button is-primary is-light"
@click="moveDown(v, i)"
>
<span class="icon">
<Icon
name="material-symbols:arrow-downward-rounded"
:size="19"
/>
</span>
</button>
</p>
<p class="control">
<button
class="button is-primary is-light"
@click="moveUp(v, i)"
>
<span class="icon">
<Icon
name="material-symbols:arrow-upward-rounded"
:size="19"
/>
</span>
</button>
</p>
<p class="control">
<button
class="button is-primary is-light"
@click="askConfirm(v, i)"
>
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="19"
/>
</span>
</button>
</p>
</div>
</td>
</tr>
</tbody>

View File

@@ -1,132 +1,190 @@
<template>
<div class="field is-horizontal">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Cỡ chữ của bảng <span class="has-text-danger"> * </span></label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="tablesetting.find((v) => v.code === 'table-font-size').detail"
@change="changeSetting($event.target.value, 'table-font-size')"
/>
</p>
<div class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-4">
<div class="box">
<label class="label fs-14">Cỡ chữ của bảng</label>
<p class="control fs-14">
<input
class="input fs-13"
type="text"
:value="tablesetting.find((v) => v.code === 'table-font-size').detail"
@change="changeSetting($event.target.value, 'table-font-size')"
/>
</p>
</div>
</div>
<div class="field">
<label class="label fs-14"> Cỡ chữ tiêu đề <span class="has-text-danger"> * </span></label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="tablesetting.find((v) => v.code === 'header-font-size').detail"
@change="changeSetting($event.target.value, 'header-font-size')"
/>
</p>
<div class="cell is-col-span-4">
<div class="box">
<label class="label fs-14">Cỡ chữ tiêu đề</label>
<p class="control">
<input
class="input fs-13"
type="text"
:value="tablesetting.find((v) => v.code === 'header-font-size').detail"
@change="changeSetting($event.target.value, 'header-font-size')"
/>
</p>
</div>
</div>
<div class="field">
<label class="label fs-14"> Số dòng trên 1 trang <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="tablesetting.find((v) => v.code === 'per-page').detail"
@change="changeSetting($event.target.value, 'per-page')"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu nền bảng <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-background').detail"
@change="changeSetting($event.target.value, 'table-background')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu chữ tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu nền tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-background').detail"
@change="changeSetting($event.target.value, 'header-background')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ khi filter<span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Đường viền <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="
tablesetting.find((v) => v.code === 'td-border')
? tablesetting.find((v) => v.code === 'td-border').detail
: undefined
"
@change="changeSetting($event.target.value, 'td-border')"
/>
</p>
<div class="cell is-col-span-4">
<div class="box">
<label class="label fs-14">Số dòng trên 1 trang</label>
<p class="control">
<input
class="input fs-13"
type="text"
:value="tablesetting.find((v) => v.code === 'per-page').detail"
@change="changeSetting($event.target.value, 'per-page')"
/>
</p>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu nền bảng</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-background').detail"
@change="changeSetting($event.target.value, 'table-background')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'table-background').detail"
@change="changeSetting($event.target.value, 'table-background')"
/>
</p>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu chữ</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')"
/>
</p>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu chữ tiêu đề</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')"
/>
</p>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu nền tiêu đề</label>
<div class="control is-flex is-gap-1 is-align-items-stretch">
<input
type="color"
:value="rgbToHex(headerBg)"
@change="changeSetting($event.target.value, 'header-background')"
/>
<input
class="input fs-13 h-full"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'header-background').detail"
@change="changeSetting($event.target.value, 'header-background')"
/>
<div
ref="renderedHeaderBg"
class="is-hidden"
:style="{
backgroundColor: tablesetting.find((v) => v.code === 'header-background').detail,
}"
></div>
</div>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu chữ khi filter</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')"
/>
</p>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Đường viền</label>
<p class="control fs-14">
<input
class="input fs-13"
type="text"
:value="
tablesetting.find((v) => v.code === 'td-border')
? tablesetting.find((v) => v.code === 'td-border').detail
: undefined
"
@change="changeSetting($event.target.value, 'td-border')"
/>
</p>
</div>
</div>
</div>
</div>
<button
class="button is-primary"
@click="emit('close')"
>
Cập nhật
</button>
</template>
<script setup>
import { useStore } from "@/stores/index";
const store = useStore();
var props = defineProps({
const props = defineProps({
pagename: String,
});
const { $copy, $clone, $empty } = useNuxtApp();
var pagedata = $clone(store[props.pagename]);
var errors = [];
var radioNote = "no";
const emit = defineEmits(["close"]);
const { $copy, $clone, $empty, $store } = useNuxtApp();
var pagedata = $clone($store[props.pagename]);
if (!Array.isArray(pagedata.tablesetting)) {
pagedata.tablesetting = Object.values(pagedata.tablesetting);
}
const errors = ref([]);
let radioNote = "no";
var tablesetting = pagedata.tablesetting;
let found = tablesetting.find((v) => v.code === "note");
if (found ? found.detail !== "@" : false) radioNote = "yes";
function changeSetting(value, code) {
if (code === "note" && $empty(value)) return;
let copy = $copy(tablesetting);
@@ -139,6 +197,45 @@ function changeSetting(value, code) {
}
tablesetting = copy;
pagedata.tablesetting = tablesetting;
store.commit(props.pagename, pagedata);
$store.commit(props.pagename, pagedata);
}
const renderedHeaderBg = useTemplateRef("renderedHeaderBg");
const headerBg = ref("#000000");
onMounted(() => {
headerBg.value = window.getComputedStyle(renderedHeaderBg.value).backgroundColor;
});
watch();
function rgbToHex(rgb) {
const [r, g, b] = rgb.match(/\d+/g);
return "#" + [r, g, b].map((x) => Number(x).toString(16).padStart(2, "0")).join("");
}
</script>
<style scoped>
input[type="color"] {
width: var(--bulma-control-height);
min-width: var(--bulma-control-height);
max-width: var(--bulma-control-height);
margin: 0 !important;
padding: 0 !important;
overflow: hidden;
}
input[type="color"]::-webkit-color-swatch-wrapper {
margin: 0 !important;
padding: 0 !important;
}
input[type="color"]::-webkit-color-swatch {
margin: 0 !important;
padding: 0 !important;
border: none;
}
.box {
box-shadow: none;
border: 1px solid var(--bulma-grey-90);
background-color: var(--bulma-grey-95);
height: 100%;
}
</style>