This commit is contained in:
oneao committed 2026-03-22 21:49:28 +08:00
1 parent aa813c9649
commit 62550b8182
45 files changed
+12535 -566

No files matched your search

@@ -0,0 +1,24 @@
import G3Detail from "./index.vue";
export function showDetailDrawer(props) {
// 1. 创建容器
const container = document.createElement("div");
document.body.appendChild(container);
// 2. 创建 app
const app = createApp({
render() {
return h(G3Detail, {
...props,
// 关闭时销毁
onClose: () => {
app.unmount();
container.remove();
},
});
},
});
// 3. 挂载
app.mount(container);
}
@@ -0,0 +1,77 @@
<script lang="js" setup>
import { loadDetailColumnsApi, loadObjectApi } from "@/request/api";
import { formatValueByFormat } from "@/utils/columns";
const loading = ref(false);
const visible = ref(true);
const props = defineProps({
viewName: String,
keyField: String,
keyValue: [String, Number],
});
const dataList = ref([]);
async function initData() {
console.log(props);
if (!props.viewName) return;
loading.value = true;
try {
const columnsRes = await loadDetailColumnsApi(props.viewName);
if (!columnsRes?.success) return;
dataList.value = columnsRes.data || [];
if (props.keyField && props.keyValue) {
const objRes = await loadObjectApi({
viewName: props.viewName,
keyField: props.keyField,
keyValue: props.keyValue,
});
if (objRes?.success) {
const rowData = objRes.data || {};
dataList.value.forEach((item) => {
item.col_Value = rowData[item.col_FieldName] ?? "";
});
}
}
} catch (e) {
console.error("initData error:", e);
} finally {
loading.value = false;
}
}
function displayValue(item) {
if (item.col_Value == null) return "";
return formatValueByFormat(item.col_Value, item.col_DisplayFormat);
}
</script>
<template>
<el-drawer v-model="visible" title="详细信息" @opened="initData">
<template #default>
<G3Grid v-if="dataList.length > 0">
<G3GridGroup>
<G3GridItem
v-for="item in dataList"
:key="item.col_FieldName"
:label="item.col_Caption"
:height="28"
:label-width="100"
>
{{ displayValue(item) }}
</G3GridItem>
</G3GridGroup>
</G3Grid>
</template>
</el-drawer>
</template>
<style lang="scss" scoped></style>
@@ -14,6 +14,11 @@ const props = defineProps({
labelWidth: {
type: [String, Number],
},
labelAlign: {
type: String,
default: "right",
validator: (v) => ["left", "center", "right"].includes(v),
},
});
const gridStyle = computed(() => ({
@@ -30,7 +35,11 @@ provide("G3GridGroupConfig", toRefs(props));
</script>
<template>
<div class="g3-grid-group" :class="{ border: props.border }" :style="gridStyle">
<div
class="g3-grid-group"
:class="{ border: props.border }"
:style="gridStyle"
>
<slot></slot>
</div>
</template>
@@ -36,7 +36,8 @@ const props = defineProps({
const parentConfig = inject("G3GridGroupConfig", {});
const mergedBorder = computed(() => {
const val = props.border !== undefined ? props.border : parentConfig.border?.value;
const val =
props.border !== undefined ? props.border : parentConfig.border?.value;
return val;
});
@@ -47,7 +48,8 @@ const gridItemStyle = computed(() => {
};
if (props.height !== undefined && props.height !== null) {
style.height = typeof props.height === "number" ? props.height + "px" : props.height;
style.height =
typeof props.height === "number" ? props.height + "px" : props.height;
}
return style;
@@ -55,8 +57,15 @@ const gridItemStyle = computed(() => {
const gridLabelStyle = computed(() => {
const val = props.labelWidth ?? parentConfig.labelWidth?.value;
const alignVal = props.labelAlign ?? parentConfig.labelAlign?.value ?? "right";
return {
flex: `0 0 ${typeof val === "number" ? val + "px" : val}`,
};
});
const gridInnerLabelStyle = computed(() => {
const alignVal =
props.labelAlign ?? parentConfig.labelAlign?.value ?? "right";
const justifyMap = {
left: "flex-start",
center: "center",
@@ -64,33 +73,32 @@ const gridLabelStyle = computed(() => {
};
return {
flex: `0 0 ${typeof val === "number" ? val + "px" : val}`,
display: "flex",
alignItems: "center",
justifyContent: justifyMap[alignVal],
};
});
const requiredStyle = computed(() => {
const alignVal = props.labelAlign ?? parentConfig.labelAlign?.value ?? "right";
return alignVal === "right" ? { flexDirection: "row-reverse" } : {};
});
</script>
<template>
<div class="g3-grid-item" :class="{ border: mergedBorder }" :style="gridItemStyle">
<div
class="g3-grid-item"
:class="{ border: mergedBorder }"
:style="gridItemStyle"
>
<!-- label -->
<div
class="label"
:class="{ required, 'is-slot': $slots.label }"
:style="[gridLabelStyle, requiredStyle]"
:class="{ 'is-slot': $slots.label }"
:style="[gridLabelStyle]"
>
<span class="label-inner">
<slot name="label">
<span v-if="required" class="required-star">*</span>
<slot name="label">
<span class="label-inner" :style="[gridInnerLabelStyle]">
{{ label }}
</slot>
</span>
</span>
</slot>
</div>
<!-- content -->
@@ -120,12 +128,12 @@ const requiredStyle = computed(() => {
display: flex;
position: relative;
min-width: 0;
width: 100%;
&.required::before {
content: "*";
.required-star {
color: #f56c6c;
margin-right: 4px;
font-weight: bold;
margin-left: 4px;
flex-shrink: 0; // 防止被压缩
}
&.is-slot .label-inner {
@@ -139,6 +147,8 @@ const requiredStyle = computed(() => {
min-width: 0;
flex: 1;
padding: 0px 4px;
display: flex;
align-items: center;
}
}
@@ -175,7 +185,7 @@ const requiredStyle = computed(() => {
:deep(.el-select) {
width: 100% !important;
height: 100% !important;
height: var(--el-component-size) !important;
.el-select__wrapper {
width: 100% !important;
height: 100% !important;
@@ -1,17 +0,0 @@
<script lang="js" setup>
defineOptions({
name: "G3RecursiveColumn",
});
const isGroup = computed(() => {
return Array.isArray(props.column.children) && props.column.children.length > 0;
});
</script>
<template>
<vxe-colgroup v-if="isGroup" :title="column.label" :field="column.field">
</vxe-colgroup>
</template>
<style lang="scss" scoped></style>
@@ -0,0 +1,53 @@
<script lang="js" setup>
import { getAlignByFormat, formatValueByFormat } from "@/utils/columns";
defineOptions({
name: "G3RecursiveColumn",
});
const props = defineProps({
column: {
type: Object,
required: true,
},
});
const isGroup = computed(() => {
return (
Array.isArray(props.column.children) && props.column.children.length > 0
);
});
</script>
<template>
<vxe-colgroup v-if="isGroup" :title="column.label" :field="column.field">
<G3RecursiveColumn
v-for="(child, index) in column.children"
:key="index"
:column="child"
></G3RecursiveColumn>
</vxe-colgroup>
<vxe-column
v-else
:field="column.col_FieldName"
:title="column.col_Caption"
:width="column.col_Width"
:align="getAlignByFormat(column.col_DisplayFormat)"
:params="column"
:sortable="props.sortable"
>
<template #default="{ row }">
{{
formatValueByFormat(row[column.col_FieldName], column.col_DisplayFormat)
}}
</template>
<template v-if="column.showSummary === '1'" #footer="{ row }">
{{
formatValueByFormat(row[column.col_FieldName], column.col_DisplayFormat)
}}
</template>
</vxe-column>
</template>
<style lang="scss" scoped></style>
@@ -9,11 +9,13 @@ const route = useRoute();
const router = useRouter();
onMounted(() => {
loadDataApi("s_keyvalue", `b_key='bodycaption'`, ``, ["b_value"]).then((res) => {
if (res.success) {
title.value = res.data[0].b_value;
}
});
loadDataApi("s_keyvalue", `b_key='bodycaption'`, ``, ["b_value"]).then(
(res) => {
if (res.success) {
title.value = res.data[0].b_value;
}
},
);
});
const menuList = ref([
@@ -40,6 +42,19 @@ const menuList = ref([
label: "系统管理",
icon: "lucide:settings",
children: [
{
label: "办公管理",
children: [
{
label: "企业公告",
path: "/system/oa/notice",
},
{
label: "用户提醒",
path: "/system/oa/reminder",
},
],
},
{
label: "字段配置",
path: "/system/field",
@@ -48,6 +63,10 @@ const menuList = ref([
label: "权限配置",
path: "/system/permission",
},
{
label: "模块配置",
path: "/system/module",
},
],
},
]);
@@ -154,13 +173,14 @@ function handleMenuChange(val) {
border-radius: 6px;
}
.el-menu-item {
padding-left: 48px !important;
// padding-left: 48px !important;
margin-bottom: 4px;
}
&.is-active {
.el-sub-menu__title {
color: var(--el-color-primary);
}
// .el-sub-menu__title {
// color: var(--el-color-primary);
// }
}
}
}
@@ -3,6 +3,7 @@ import { useUserStore } from "@/store/user";
import { useTabStore } from "@/store/tab";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import UpdatePassword from "./update-password.vue";
const props = defineProps({
collapse: {
@@ -18,6 +19,8 @@ const router = useRouter();
const tabStore = useTabStore();
const userStore = useUserStore();
const showUpdatePassword = ref(false);
function onCollapse() {
emits("collapse");
}
@@ -37,7 +40,11 @@ function logout() {
<div class="g3-topbar-left">
<div class="g3-action-icon" @click="onCollapse()">
<G3Icon
:icon="props.collapse ? 'lucide:list-indent-increase' : 'lucide:list-indent-decrease'"
:icon="
props.collapse
? 'lucide:list-indent-increase'
: 'lucide:list-indent-decrease'
"
/>
</div>
</div>
@@ -48,19 +55,29 @@ function logout() {
<G3Icon icon="lucide:circle-user-round" size="18" />
<div class="user-info">
<span class="user-name">
{{ userStore.userInfo.username || userStore.userInfo.userid || "空" }}
{{
userStore.userInfo.username || userStore.userInfo.userid || "空"
}}
</span>
</div>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item> 个人信息 </el-dropdown-item>
<el-dropdown-item> 修改密码 </el-dropdown-item>
<el-dropdown-item divided @click="logout"> 退出登录 </el-dropdown-item>
<el-dropdown-item @click="router.push('/user-center')"
>个人中心</el-dropdown-item
>
<el-dropdown-item @click="showUpdatePassword = !showUpdatePassword">
修改密码
</el-dropdown-item>
<el-dropdown-item divided @click="logout">
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<UpdatePassword v-model="showUpdatePassword" />
</div>
</template>
@@ -0,0 +1,90 @@
<script lang="js" setup>
import { updatePasswordApi } from "@/request/api";
import { ElMessage } from "element-plus";
const confirmLoading = ref(false);
const showDialog = defineModel(false);
const form = ref({
oldPassword: "",
newPassword: "",
confirmPassword: "",
});
function handleConfirm() {
if (form.value.newPassword === "") {
ElMessage.warning("新密码不能为空");
return;
}
if (form.value.confirmPassword !== form.value.newPassword) {
ElMessage.warning("新密码和确认密码不一致");
return;
}
confirmLoading.value = true;
updatePasswordApi({
newPassword: form.value.newPassword,
oldPassword: form.value.oldPassword,
})
.then((res) => {
if (res.success) {
// 修改成功
ElMessage.success("修改成功");
showDialog.value = false;
}
})
.finally(() => {
confirmLoading.value = false;
});
}
function resetForm() {
form.value.newPassword = "";
form.value.oldPassword = "";
form.value.confirmPassword = "";
}
</script>
<template>
<el-dialog
v-model="showDialog"
@closed="resetForm"
title="修改密码"
width="400"
>
<G3Grid>
<G3GridGroup :label-width="80" :column="1" label-align="right">
<G3GridItem label="旧密码" :span="1">
<el-input v-model="form.oldPassword" type="password" show-password />
</G3GridItem>
<G3GridItem label="新密码" :span="1" required>
<el-input v-model="form.newPassword" type="password" show-password />
</G3GridItem>
<G3GridItem label="确认密码" :span="1" required>
<el-input
v-model="form.confirmPassword"
type="password"
show-password
/>
</G3GridItem>
</G3GridGroup>
</G3Grid>
<div class="footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="confirmLoading" @click="handleConfirm"
>确认</el-button
>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.footer {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
}
</style>
@@ -9,6 +9,13 @@ export function loginApi(orgid, userid, password) {
});
}
export function updatePasswordApi({ oldPassword, newPassword }) {
return httpPost(`/auth/updatePassword`, {
oldPassword,
newPassword,
});
}
// ============================= data =============================
export function loadDataApi(viewName, searchCondition = "", orderField = "", searchColumns = []) {
return httpPost("/data/loadData", {
@@ -25,6 +32,22 @@ export function loadDataBySqlApi(sql) {
});
}
export function loadObjectApi({
viewName,
keyField,
keyValue,
searchCondition = "",
orderField = "",
}) {
return httpPost("/data/loadObject", {
viewName,
keyField,
keyValue,
searchCondition,
orderField,
});
}
/**
* 分页加载表格数据
* @param {Object} params 参数对象
@@ -71,6 +94,13 @@ export function saveObjectApi(data) {
export function saveObjectKeysApi(data) {
return httpPost("/data/saveObjectKeys", data);
}
export function deleteModuleApi({ moduleId, Id }) {
return httpPost("/data/deleteModule", {
moduleId,
Id,
});
}
// ============================= data =============================
// ============================= column =============================
@@ -92,9 +122,19 @@ export function loadAllColumnsApi(viewName) {
});
}
export function loadDetailColumnsApi(viewName) {
return httpPost("/column/detail", {
viewName,
});
}
export function checkColumnsApi(viewName) {
return httpPost("/column/check", {
viewName,
});
}
export function updateColumnsApi(columns) {
return httpPost("/column/updateColumns", columns);
}
// ============================= column =============================
@@ -31,7 +31,7 @@ export const routes = [
keepAlive: true,
},
},
{
{
path: "/system/permission",
component: () => import("@/views/system/permission/index.vue"),
meta: {
@@ -39,6 +39,39 @@ export const routes = [
keepAlive: true,
},
},
{
path: "/system/module",
component: () => import("@/views/system/module/index.vue"),
meta: {
title: "模块设置",
keepAlive: true,
},
},
{
path: "/system/oa/notice",
component: () => import("@/views/system/oa/notice/index.vue"),
meta: {
title: "企业公告",
keepAlive: true,
},
},
{
path: "/system/oa/reminder",
component: () => import("@/views/system/oa/reminder/index.vue"),
meta: {
title: "用户提醒",
keepAlive: true,
},
},
// ==================== 个人中心 ====================
{
path: "/user-center",
component: () => import("@/views/user-center/index.vue"),
meta: {
title: "个人中心",
keepAlive: false,
},
},
],
},
{
@@ -0,0 +1,113 @@
import { updateColumnsApi } from "@/request/api";
import { ElMessage } from "element-plus";
export function updateEditTableColumns(tableRef) {
const $table = unref(tableRef);
if (!$table || typeof $table.getColumns !== "function") {
ElMessage.warning("表格尚未初始化完成");
return;
}
const columns = $table.getColumns();
if (!$table || $table.length === 0) {
ElMessage.warning("未获取到栏位信息");
return;
}
const newColumns = columns
.filter((item) => item.params.b_id !== undefined)
.map((item, index) => ({
b_id: item.params.b_id,
col_edit_width: item.renderWidth,
col_edit_position: index,
}));
updateColumnsApi(newColumns).then((res) => {
if (res.success) {
ElMessage.success("保存成功");
}
});
}
export function updateViewTableColumns(tableRef) {
const $table = unref(tableRef);
if (!$table || typeof $table.getColumns !== "function") {
ElMessage.warning("表格尚未初始化完成");
return;
}
const columns = $table.getColumns();
if (!$table || $table.length === 0) {
ElMessage.warning("未获取到栏位信息");
return;
}
const newColumns = columns
.filter((item) => item.params.b_id !== undefined)
.map((item, index) => ({
b_id: item.params.b_id,
col_Width: item.renderWidth,
col_Position: index,
}));
updateColumnsApi(newColumns).then((res) => {
if (res.success) {
ElMessage.success("保存成功");
}
});
}
/**
* 根据 format 判断列对齐方式
* @param {string} format
*/
export function getAlignByFormat(format) {
if (!format || typeof format !== "string") return "left";
const fmt = format.toLowerCase();
if (fmt.startsWith("money") || fmt.startsWith("number")) {
return "right";
}
if (fmt === "checkbox") {
return "center";
}
return "left";
}
/**
* 根据 format 格式化值
* @param {any} value 原始值
* @param {string} format 格式描述,如 money(2)、percent(1)
*/
export function formatValueByFormat(value, format) {
if (value === null || value === undefined || value === "") return "";
if (!format || typeof format !== "string") return value;
const fmt = format.trim().toLowerCase();
// money(n)
if (fmt.startsWith("money")) {
const match = fmt.match(/^money\((\d+)\)$/);
const decimals = match ? Number(match[1]) : 2;
const num = Number.parseFloat(value);
if (Number.isNaN(num)) return value;
// 使用Intl.NumberFormat来处理千分位
return new Intl.NumberFormat("en-US", {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(num);
} else if (fmt.startsWith("date")) {
return formatToDate(value);
} else if (fmt.startsWith("number")) {
return Number(value) || 0;
}
return value;
}
@@ -0,0 +1,21 @@
import dayjs from "dayjs";
export function formatToDate(date) {
const validDate = dayjs(date);
if (!validDate.isValid()) {
return date;
}
return validDate.format("YYYY-MM-DD");
}
export function formatToDateTime(date) {
const validDate = dayjs(date);
if (!validDate.isValid()) {
return date;
}
return validDate.format("YYYY-MM-DD HH:mm:ss");
}
export function getDateTime() {
return dayjs().format("YYYY-MM-DD HH:mm:ss");
}
@@ -7,9 +7,14 @@ import {
saveObjectApi,
} from "@/request/api";
import { ElMessage } from "element-plus";
import PreviewSql from "./preview-sql.vue";
import { diffObject } from "@/utils/diff";
import PreviewSql from "./preview-sql.vue";
import PreviewEdit from "./preview-edit.vue";
import PreviewView from "./preview-view.vue";
import TagUpdate from "./tag-update.vue";
import { showDetailDrawer } from "@/components/g3-detail";
const tableRef = ref();
const loading = ref(false);
@@ -19,7 +24,9 @@ const isCollapse = ref(false);
const showSql = ref(false);
const treeData = [{ b_id: "root", mc_b: "全部分类", isLeaf: false, children: [] }];
const treeData = [
{ b_id: "root", mc_b: "全部分类", isLeaf: false, children: [] },
];
const treeProps = {
children: "children",
@@ -35,122 +42,152 @@ const currentRow = ref({});
const isSelectedRow = computed(() => !!currentRow.value?.col_FieldName);
let oldRowData = {};
const showPreview = ref(false);
const showEditPreview = ref(false);
const showTag = ref(false);
const columns = [
{
prop: "col_field_name",
prop: "col_FieldName",
label: "字段",
width: 140,
fixed: "left",
},
{
prop: "col_caption",
prop: "col_Caption",
label: "标题",
width: 100,
fixed: "left",
type: "input",
},
{
prop: "col_field_name_v",
prop: "col_FieldName_v",
label: "视图字段",
width: 100,
type: "input",
},
{
prop: "col_group_index",
prop: "col_BandIndex",
label: "分组",
width: 80,
type: "input",
},
{
prop: "col_visible",
prop: "col_Visible",
label: "显示",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_readonly",
prop: "col_Color",
label: "不创建",
width: 80,
type: "checkbox",
},
{
prop: "col_ReadOnly",
label: "只读",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_null",
prop: "col_AllowNull",
label: "可不输",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_repeat",
prop: "col_AllowRepeat",
label: "可重复",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_reserve",
prop: "col_NeedReserve",
label: "可保留",
width: 80,
type: "checkbox",
},
{
prop: "col_search",
prop: "col_CanSearch",
label: "可查找",
width: 80,
type: "checkbox",
},
{
prop: "col_search_order",
prop: "col_SearchOrder",
label: "查找顺序",
width: 100,
type: "input",
},
{
prop: "col_search_group_index",
label: "查找分组",
prop: "col_type",
label: "查找类型",
width: 100,
type: "input",
},
{
prop: "col_type",
label: "字段类型",
width: 100,
type: "option",
},
{
prop: "col_default_value",
prop: "col_DefaultValue",
label: "默认值",
width: 80,
type: "input",
},
{
prop: "col_width",
prop: "col_Width",
label: "宽度",
width: 80,
type: "input",
},
{
prop: "col_position",
prop: "col_Position",
label: "位置",
width: 80,
type: "input",
},
{
prop: "col_show_summary",
prop: "col_ShowSummary",
label: "合计",
width: 80,
type: "checkbox",
},
{
prop: "col_display_format",
prop: "col_foot",
label: "合计内容",
width: 100,
type: "input",
},
{
prop: "col_DisplayFormat",
label: "显示格式",
width: 100,
type: "input",
},
{
prop: "col_SelectQuery",
label: "下拉查询",
width: 120,
type: "input",
},
{
prop: "col_SelectQueryDefault",
label: "下拉查询默认",
width: 80,
type: "input",
},
{
prop: "col_edit",
label: "编辑",
width: 80,
type: "checkbox",
},
{
prop: "col_edit_type",
label: "编辑格式",
width: 100,
type: "option",
},
{
prop: "col_edit_position",
label: "编辑位置",
@@ -170,8 +207,14 @@ const columns = [
type: "input",
},
{
prop: "col_edit_source_data",
label: "编辑数据",
prop: "col_edit_total_column",
label: "几列",
width: 70,
type: "input",
},
{
prop: "col_edit_source_table",
label: "编辑下拉表",
width: 160,
type: "input",
},
@@ -182,7 +225,7 @@ const columns = [
type: "input",
},
{
prop: "col_edit_group_index",
prop: "col_edit_grouping",
label: "编辑分组",
width: 100,
type: "input",
@@ -194,7 +237,7 @@ const columns = [
type: "checkbox",
},
{
prop: "col_show_property",
prop: "col_ShowDetail",
label: "属性",
width: 80,
type: "checkbox",
@@ -246,16 +289,6 @@ function getOptions(field) {
}
}
function updateCollapse() {
if (isCollapse.value) {
isCollapse.value = false;
leftSize.value = 200;
} else {
isCollapse.value = true;
leftSize.value = 0;
}
}
async function handleTreeLoad(node, resolve) {
try {
const { level: nodeLevel, data: nodeData } = node;
@@ -317,7 +350,10 @@ async function handleTreeNodeClick(nodeData) {
loading.value = true;
try {
const res = await loadDataApi("s_module", `b_id = '${nodeData.b_moduleid}'`);
const res = await loadDataApi(
"s_module",
`b_id = '${nodeData.b_moduleid}'`,
);
if (res.success) {
currentModule.value = res.data[0] || null;
@@ -455,14 +491,49 @@ function handleCheckColumns() {
<div class="content">
<div class="content-head">
<div class="content-head-left">
<el-button type="primary" :disabled="!isSelectedNode" @click="handleCheckColumns"
<el-button
type="primary"
:disabled="!isSelectedNode"
@click="handleCheckColumns"
>检查</el-button
>
<el-button type="primary" :disabled="!isSelectedNode">浏览栏位预览</el-button>
<el-button type="primary" :disabled="!isSelectedNode">编辑栏位预览</el-button>
<el-button type="primary" :disabled="!isSelectedNode">属性预览</el-button>
<el-button type="primary">修改标签</el-button>
<el-button type="primary" :disabled="!isSelectedNode" @click="showSql = !showSql">
<el-button
type="primary"
:disabled="!isSelectedNode"
@click="showPreview = true"
>
浏览栏位预览
</el-button>
<el-button
type="primary"
:disabled="!isSelectedNode"
@click="showEditPreview = true"
>
编辑栏位预览
</el-button>
<el-button
type="primary"
:disabled="!isSelectedNode"
@click="
showDetailDrawer({
viewName: currentModule?.b_ViewTable,
})
"
>
属性预览
</el-button>
<el-button
type="primary"
:disabled="!isSelectedRow"
@click="showTag = !showTag"
>
修改标签
</el-button>
<el-button
type="primary"
:disabled="!isSelectedNode"
@click="showSql = !showSql"
>
SQL语句
</el-button>
</div>
@@ -570,7 +641,23 @@ function handleCheckColumns() {
</el-splitter-panel>
</el-splitter>
<PreviewSql v-model="showSql" :data="tableData" :viewtable="currentModule?.b_ViewTable" />
<PreviewSql
v-model="showSql"
:data="tableData"
:view-name="currentModule?.b_ViewTable"
/>
<PreviewEdit
v-model="showEditPreview"
:view-name="currentModule?.b_ViewTable"
/>
<PreviewView
v-model="showPreview"
:view-name="currentModule?.b_ViewTable"
/>
<TagUpdate v-model="showTag" :data="currentRow" />
</div>
</template>
@@ -699,7 +786,7 @@ function handleCheckColumns() {
.content-head-right {
display: flex;
@include flexGapWrap(4px, 0px);
@include flexGapWrap(4px, 8px);
flex-wrap: nowrap;
min-width: 200px;
}
@@ -0,0 +1,96 @@
<script lang="js" setup>
import { loadEditColumnsApi } from "@/request/api";
import { updateEditTableColumns } from "@/utils/columns";
const props = defineProps({
viewName: {
type: String,
},
});
const emits = defineEmits(["change"]);
const tableRef = ref(null);
const loading = ref(false);
const columns = ref([]);
async function initData() {
loading.value = true;
const result = await loadEditColumnsApi(props.viewName);
if (result.success) {
columns.value = result.data;
loading.value = false;
}
}
</script>
<template>
<el-dialog
v-bind="$attrs"
title="预览栏位排序效果"
width="1200px"
class="dialog"
@opened="initData"
>
<div class="g3-dialog">
<div class="g3-actions">
<el-button
type="primary"
:loading="loading"
@click="updateEditTableColumns(tableRef)"
>
栏位保存
</el-button>
</div>
<div class="g3-table">
<vxe-table
ref="tableRef"
border
header-align="center"
:loading="loading"
:column-drag-config="{
showIcon: false,
trigger: 'cell',
}"
:row-config="{
useKey: true,
isCurrent: true,
isHover: true,
}"
:column-config="{
resizable: true,
drag: true,
}"
show-header-overflow
height="100%"
>
<vxe-column
v-for="column in columns"
:key="column.b_id"
:field="column.col_FieldName"
:title="column.col_Caption"
:width="column.col_edit_width"
:params="column"
align="center"
/>
</vxe-table>
</div>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.g3-dialog {
display: flex;
flex-direction: column;
height: 500px;
}
.g3-table {
flex: 1;
margin-top: 8px;
}
</style>
@@ -12,7 +12,7 @@ const props = defineProps({
data: {
type: null,
},
viewtable: {
viewName: {
type: null,
},
});
@@ -20,25 +20,25 @@ const props = defineProps({
const sqlStr = ref("");
function initData() {
if (!props.viewtable) return;
if (!props.viewName) return;
const vrows = props.data || [];
const sqlArr = [];
// 删除语句
const deleteSql = `DELETE FROM s_columnlib WHERE col_TableName='${props.viewtable}';`;
const deleteSql = `DELETE FROM s_columnLib WHERE col_TableName='${props.viewName}';`;
sqlArr.push(deleteSql);
// 插入语句
vrows.forEach((row) => {
const insertSql =
"INSERT INTO s_columnlib(col_TableName,col_FieldName,col_Caption,col_Width,col_Visible,col_Position," +
"INSERT INTO s_columnLib(col_TableName,col_FieldName,col_Caption,col_Width,col_Visible,col_Position," +
"col_ShowSummary,col_BandIndex,col_AllowNull,col_AllowRepeat,col_DefaultValue,col_ReadOnly,col_Color," +
"col_CanSearch,col_SearchOrder,col_NeedReserve,col_sorted,col_DisplayFormat,col_type,col_datatype,col_foot)";
const valueSql =
`VALUES('${props.viewtable}', '${row.col_FieldName}', '${row.col_Caption}', '${row.col_Width}', '${row.col_Visible}', '${row.col_Position}', ` +
`VALUES('${props.viewName}', '${row.col_FieldName}', '${row.col_Caption}', '${row.col_Width}', '${row.col_Visible}', '${row.col_Position}', ` +
`'${row.col_ShowSummary}', '${row.col_BandIndex}', '${row.col_AllowNull}', '${row.col_AllowRepeat}', '${row.col_DefaultValue}', '${row.col_ReadOnly}', '${row.col_Color}', ` +
`'${row.col_CanSearch}', '${row.col_SearchOrder}', '${row.col_NeedReserve}', '${row.col_sorted}', '${row.col_DisplayFormat}', '${row.col_type}', '${row.col_datatype}', '${row.col_foot}');`;
@@ -1,7 +1,131 @@
<script lang="js" setup></script>
<script lang="js" setup>
import G3RecursiveColumn from "@/components/g3-table/g3-recursive-column.vue";
import { loadViewColumnsApi } from "@/request/api";
import { updateViewTableColumns } from "@/utils/columns";
const props = defineProps({
viewName: {
type: String,
},
});
const tableRef = ref(null);
const loading = ref(false);
const columns = ref([]);
async function initData() {
loading.value = true;
const result = await loadViewColumnsApi(props.viewName);
if (result.success) {
const datas = result.data;
columns.value = [];
datas.forEach((item) => {
if (item.col_BandIndex === 0) {
columns.value.push(item);
} else {
let parent = columns.value.find(
(col) => col.label === item.col_BandIndex,
);
if (!parent) {
parent = {
label: item.col_BandIndex,
children: [],
};
columns.value.push(parent);
}
parent.children.push(item);
}
});
loading.value = false;
}
}
function onClosed() {
loading.value = true;
columns.value = [];
}
</script>
<template>
<div class=""></div>
<el-dialog
v-bind="$attrs"
title="预览栏位排序效果"
width="1200px"
class="dialog"
@opened="initData"
@closed="onClosed"
>
<div class="g3-dialog">
<div class="g3-actions">
<el-button
type="primary"
:loading="loading"
@click="updateViewTableColumns(tableRef)"
>
栏位保存
</el-button>
</div>
<div class="g3-table">
<vxe-table
ref="tableRef"
border
stripe
header-align="center"
:loading="loading"
:filter-config="{
showIcon: false,
}"
:column-drag-config="{
showIcon: false,
trigger: 'cell',
}"
:row-config="{
useKey: true,
isCurrent: true,
isHover: true,
}"
size="small"
:cell-config="{
height: 26,
}"
:column-config="{
resizable: true,
drag: true,
}"
show-overflow
show-header-overflow
height="100%"
>
<g3-recursive-column
v-for="(column, index) in columns"
:key="index"
:column="column"
:sortable="false"
/>
</vxe-table>
</div>
</div>
</el-dialog>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.g3-dialog {
display: flex;
flex-direction: column;
height: 500px;
}
.g3-table {
flex: 1;
margin-top: 8px;
}
</style>
@@ -0,0 +1,140 @@
<script lang="js" setup>
import { loadDataApi, saveObjectApi, saveObjectKeysApi } from "@/request/api";
const props = defineProps({
data: {
type: null,
},
});
const tableRef = ref();
const visible = defineModel(false);
const loading = reactive({
table: true,
save: false,
});
const tableData = ref([]);
async function initData() {
const fieldName = props.data?.col_FieldName;
console.log(props.data);
if (!fieldName) return;
loading.table = true;
const response = await loadDataApi(
"s_columnLib",
`col_FieldName = '${fieldName}'`,
);
if (response.success) {
tableData.value = (response.data || []).filter(
(item) => item.col_TableName !== props.data.col_TableName,
);
loading.table = false;
}
}
function onClosed() {
tableData.value = [];
visible.value = false;
}
async function handleSave() {
const selectedRows = tableRef.value.getCheckboxRecords();
loading.save = true;
const caption = props.data?.col_Caption || "";
// 构建更新数据
const updates = selectedRows.map((row) => ({
b_id: row.b_id,
col_Caption: caption,
}));
const requestData = [
{
tableName: "s_columnLib",
keyField: "b_id",
inserts: [],
updates,
deletes: [],
log: "0",
user: "0",
},
];
const response = await saveObjectApi(requestData);
if (response.success) {
ElMessage.success("保存成功");
onClosed();
}
}
</script>
<template>
<el-dialog
v-bind="$attrs"
v-model="visible"
title="修改栏位标签"
width="1000px"
class="dialog"
@opened="initData"
@closed="onClosed"
>
<vxe-table
ref="tableRef"
height="400"
:data="tableData"
:loading="loading.table"
:row-config="{
useKey: true,
isCurrent: true,
}"
:column-config="{
resizable: true,
useKey: true,
}"
:edit-config="{
trigger: 'click',
mode: 'row',
autoClear: false,
showStatus: false,
showIcon: false,
}"
:scroll-x="{
gt: 0,
enabled: true,
}"
:scroll-y="{
gt: 0,
enabled: true,
}"
show-overflow
show-header-overflow
>
<vxe-column type="checkbox" width="50" align="center" fixed="left" />
<vxe-column field="col_TableName" title="视图" align="center" />
<vxe-column field="col_FieldName" title="字段" align="center" />
<vxe-column field="col_Caption" title="标题" align="center" />
<vxe-column field="col_Visible" title="显示" align="center" />
<vxe-column field="col_Width" title="宽度" align="center" />
</vxe-table>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false"> 取消 </el-button>
<el-button type="primary" :loading="loading.save" @click="handleSave">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped></style>
@@ -0,0 +1,177 @@
<script setup>
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";
// Props
const props = defineProps({
visible: Boolean,
mode: { type: String, default: "add" }, // add | edit
node: { type: Object, default: () => ({}) }, // 当前节点数据(edit时使用)
parent: { type: Object, default: () => ({}) },
});
// Emits
const emit = defineEmits(["update:visible", "submit"]);
// Local state
const form = ref({
p_id: "",
p_name: "",
bm_b: "",
mc_b: "",
b_xh: "",
b_canuse: "",
b_canmenu: "",
});
const visible = ref(props.visible);
const isDisabled = ref(false); // 控制是否禁用
// 同步外部 v-model
watch(
() => props.visible,
(val) => (visible.value = val),
);
watch(visible, (val) => emit("update:visible", val));
//编辑时回填
watch(
() => props.node,
(val) => {
if (val) {
if (props.mode == "add") {
isDisabled.value = false;
if (val.id != "all") {
form.value.p_id = val.id;
form.value.p_name = val.label;
} else {
form.value.p_id = "";
form.value.p_name = "";
}
}
if (props.mode == "edit") {
isDisabled.value = true;
if (val.id != "all") {
if (props.parent.id != "all") {
form.value.p_id = props.parent.id;
form.value.p_name = props.parent.label;
} else {
form.value.p_id = "";
form.value.p_name = "";
}
form.value.bm_b = val.id;
form.value.mc_b = val.label;
form.value.b_xh = val.b_xh;
form.value.b_canuse = Number(val.b_canuse);
form.value.b_canmenu = Number(val.b_canmenu);
} else {
form.value.p_id = "";
form.value.p_name = "";
}
}
}
},
{ immediate: true },
);
// 提交
const handleSubmit = () => {
if (!form.value.bm_b || !form.value.mc_b) {
ElMessage.warning("本级编码、名称不能为空");
return;
}
emit("submit", { ...form.value });
visible.value = false;
};
const resetForm = () => {
form.value.p_id = "";
form.value.p_name = "";
form.value.bm_b = "";
form.value.mc_b = "";
form.value.b_xh = "";
form.value.b_canuse = "";
form.value.b_canmenu = "";
};
</script>
<template>
<el-dialog
:title="mode === 'add' ? '新增分类' : '修改分类'"
v-model="visible"
width="400px"
@close="resetForm"
>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">上级编码:</label>
<el-input type="text" v-model="form.p_id" style="width: 280px" disabled />
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">上级名称:</label>
<el-input
type="text"
v-model="form.p_name"
style="width: 280px"
disabled
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">本级编码:</label>
<el-input
type="text"
v-model="form.bm_b"
placeholder="请输入本级编码"
style="width: 280px"
:disabled="isDisabled"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">本级名称:</label>
<el-input
type="text"
v-model="form.mc_b"
placeholder="请输入本级名称"
style="width: 280px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">排序:</label>
<el-input
type="text"
v-model="form.b_xh"
placeholder="请输入排序"
style="width: 280px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">主菜单显示:</label>
<el-checkbox
v-model="form.b_canmenu"
:true-value="1"
:false-value="0"
size="large"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">目录显示:</label>
<el-checkbox
v-model="form.b_canuse"
:true-value="1"
:false-value="0"
size="large"
/>
</div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<style scoped>
.font-medium {
font-weight: 500;
width: 74px;
text-align: right;
margin-right: 3px;
}
</style>
@@ -0,0 +1,244 @@
<script lang="js" setup>
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";
// Props
const props = defineProps({
visible: Boolean,
mode: { type: String, default: "add" }, // add | edit
node: { type: Object, default: () => ({}) }, // 当前节点数据(edit时使用)
});
// Emits
const emit = defineEmits(["update:visible", "submit"]);
// Local state
const form = ref({});
const visible = ref(props.visible);
const isDisabled = ref(false); // 控制是否禁用
// 同步外部 v-model
watch(
() => props.visible,
(val) => (visible.value = val),
);
watch(visible, (val) => emit("update:visible", val));
//编辑时回填
watch(
() => props.node,
(val) => {
if (val) {
if (props.mode == "add") {
isDisabled.value = false;
form.value = val;
}
if (props.mode == "edit") {
isDisabled.value = true;
form.value = val;
}
}
},
{ immediate: true },
);
// 提交
const handleSubmit = () => {
if (!form.value.b_id || !form.value.b_Cate_ID) {
ElMessage.warning("模块代码、类别不能为空");
return;
}
emit("submit", { ...form.value });
visible.value = false;
};
const resetForm = () => {
form.value.p_id = "";
form.value.p_name = "";
form.value.bm_b = "";
form.value.mc_b = "";
form.value.b_xh = "";
form.value.b_canuse = "";
};
</script>
<template>
<el-dialog
:title="mode === 'add' ? '新增分类' : '修改分类'"
v-model="visible"
width="500px"
@close="resetForm"
>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">模块代码:</label>
<el-input
type="text"
v-model="form.b_id"
placeholder="请输入模块代码"
style="width: 374px"
:disabled="isDisabled"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">描述:</label>
<el-input
type="text"
v-model="form.b_ChineseName"
placeholder="请输入描述"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">视图:</label>
<el-input
type="text"
v-model="form.b_ViewTable"
placeholder="请输入视图"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">表名:</label>
<el-input
type="text"
v-model="form.b_SaveTable"
placeholder="请输入表名"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">关联模块:</label>
<el-input
type="text"
v-model="form.b_LinkTable"
placeholder="请输入关联模块"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">排序字段:</label>
<el-input
type="text"
v-model="form.b_OrderField"
placeholder="请输入排序字段"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">删除条件:</label>
<el-input
type="textarea"
v-model="form.b_JudgeCondition"
placeholder="请输入删除条件"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">提示信息:</label>
<el-input
type="text"
v-model="form.b_Message"
placeholder="请输入提示信息"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">默认显示条件:</label>
<el-input
type="text"
v-model="form.b_DefaultCondition"
placeholder="请输入默认显示条件"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">编码分类表:</label>
<el-input
type="text"
v-model="form.b_BMFL"
placeholder="请输入编码分类表"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">类别:</label>
<el-input
type="text"
v-model="form.b_Cate_ID"
placeholder="请输入类别"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">顺序:</label>
<el-input
type="text"
v-model="form.b_Order"
placeholder="请输入顺序"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">编辑路径:</label>
<el-input
type="text"
v-model="form.b_editfile"
placeholder="请输入编辑路径"
style="width: 374px"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">主模块</label>
<el-checkbox
v-model="form.b_isMainModule"
:true-label="1"
:false-label="0"
size="large"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">虚拟</label>
<el-checkbox
v-model="form.b_ISVirtualModule"
:true-value="1"
:false-value="0"
size="large"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">自动编码</label>
<el-checkbox
v-model="form.b_autoid"
:true-value="1"
:false-value="0"
size="large"
/>
</div>
<div style="display: flex; margin-top: 3px; align-items: center">
<label class="font-medium">审核中心</label>
<el-checkbox
v-model="form.b_SH"
:true-value="1"
:false-value="0"
size="large"
/>
</div>
</div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<style scoped>
.font-medium {
font-weight: 500;
width: 90px;
text-align: right;
margin-right: 3px;
}
</style>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,473 @@
<script setup>
import { loadDataApi, saveObjectApi } from "@/request/api";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({
modelValue: Boolean,
data: {
type: Object,
required: true,
default: () => ({}),
},
});
const loading = reactive({
table: true,
save: false,
});
const emit = defineEmits(["update:modelValue"]);
const visible = ref(props.modelValue);
const tableData = ref([]);
const selectedRows = ref([]);
const deletedRows = ref([]);
const activeGroup = ref(null);
watch(
() => props.modelValue,
(v) => (visible.value = v),
);
watch(visible, (v) => emit("update:modelValue", v));
// 计算属性:获取所有不重复的组名
const groupList = computed(() => {
const groups = new Set();
tableData.value.forEach((row) => {
groups.add(row.b_groupby || "");
});
return Array.from(groups).sort();
});
// 计算属性:根据当前选中的组过滤数据
const filteredTableData = computed(() => {
if (!activeGroup.value) return tableData.value;
return tableData.value.filter(
(row) =>
(row.b_groupby || "") ===
(activeGroup.value === "" ? null : activeGroup.value),
);
});
// 获取某个组的记录数量
const getGroupCount = (group) => {
return tableData.value.filter(
(row) => (row.b_groupby || "") === (group === "" ? null : group),
).length;
};
const handleSelectionChange = (rows) => {
selectedRows.value = rows;
};
// 按组过滤数据
const filterByGroup = (group) => {
activeGroup.value = group === activeGroup.value ? null : group;
};
async function initData() {
loading.table = true;
try {
const response = await loadDataApi("s_keyvalue");
if (response.success) {
tableData.value = (response.data || [])
.filter((item) => !props.data?.b_key || item.b_key !== props.data.b_key)
.map((item) => ({
...item,
isEditing: false,
isModified: false,
isNew: false,
}));
deletedRows.value = [];
activeGroup.value = null;
} else {
tableData.value = [];
ElMessage.error(response.data?.message || "加载列信息失败");
}
} catch (err) {
tableData.value = [];
} finally {
loading.table = false;
}
}
const close = (resetData = true) => {
if (resetData) {
tableData.value = [];
selectedRows.value = [];
deletedRows.value = [];
activeGroup.value = null;
}
visible.value = false;
};
const handleSave = async () => {
// 验证所有修改过的行
const invalidRows = tableData.value.filter(
(row) =>
(row.isModified || row.isNew) &&
(!row.b_key?.trim() || !row.b_value?.trim()),
);
if (invalidRows.length > 0) {
ElMessage.error("请填写完整的键和值");
return;
}
loading.save = true;
// 构建保存数据
const updates = tableData.value
.filter((row) => row.isModified && !row.isNew)
.map((row) => ({
b_key: row.b_key,
b_value: row.b_value,
b_groupby: row.b_groupby,
b_bz: row.b_bz,
}));
const inserts = tableData.value
.filter((row) => row.isNew)
.map((row) => ({
b_key: row.b_key,
b_value: row.b_value,
b_groupby: row.b_groupby,
b_bz: row.b_bz,
}));
const deletes = deletedRows.value.map((row) => ({
b_key: row.b_key,
}));
const requestData = [
{
tableName: "s_keyvalue",
keyField: "b_key",
inserts,
updates,
deletes,
},
];
try {
const response = await saveObjectApi(requestData);
if (response.success) {
ElMessage.success("保存成功");
await initData(); // 重新加载数据,这会更新组列表
} else {
throw new Error(response.message || "保存失败");
}
} catch (err) {
console.error("保存出错:", err);
} finally {
loading.save = false;
}
};
function addRowAbove(index) {
const newRow = {
b_key: "",
b_value: "",
b_groupby:
activeGroup.value === ""
? null
: activeGroup.value || props.data?.b_groupby || "",
b_bz: "",
isEditing: true,
isModified: true,
isNew: true,
};
tableData.value.splice(index, 0, newRow);
nextTick(() => {
const input = document.querySelector(
`.el-table__body tr:nth-child(${index + 1}) .el-input__inner`,
);
input?.focus();
});
}
function toggleEdit(row) {
row.isEditing = !row.isEditing;
if (row.isEditing && !row.isNew) {
row.isModified = true;
}
nextTick(() => {
const input = document.querySelector(".el-table__body .el-input__inner");
input?.focus();
});
}
const deleteRow = (row, index) => {
ElMessageBox.confirm("确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
if (!row.isNew) {
deletedRows.value.push(row); // 记录已删除的行
}
tableData.value.splice(index, 1);
ElMessage.success("删除成功");
});
};
function validateField(row, field) {
if (field === "b_key" && !row.b_key.trim()) {
ElMessage.warning("键不能为空");
return false;
}
if (field === "b_value" && !row.b_value.trim()) {
ElMessage.warning("值不能为空");
return false;
}
return true;
}
</script>
<template>
<el-dialog
v-bind="$attrs"
v-model="visible"
title="键值设置"
width="1200px"
class="dialog"
@open="initData"
>
<div class="content-wrapper">
<!-- 左侧组列表 -->
<div class="group-list">
<div class="group-header">组列表</div>
<el-scrollbar class="group-scroll">
<div
v-for="group in groupList"
:key="group"
class="group-item"
:class="{ active: activeGroup === group }"
@click="filterByGroup(group)"
>
{{ group || "未分组" }}
<span class="count-badge">{{ getGroupCount(group) }}</span>
</div>
</el-scrollbar>
</div>
<!-- 右侧表格 -->
<div class="table-container">
<el-table
v-loading="loading.table"
:data="filteredTableData"
border
class="table"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" align="center" fixed />
<el-table-column prop="b_key" label="键" align="center" width="180">
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_key"
size="small"
@blur="validateField(row, 'b_key')"
@click.stop
/>
<span v-else>{{ row.b_key }}</span>
</template>
</el-table-column>
<el-table-column prop="b_value" label="值" align="center" width="180">
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_value"
size="small"
@blur="validateField(row, 'b_value')"
@click.stop
/>
<span v-else>{{ row.b_value }}</span>
</template>
</el-table-column>
<el-table-column prop="b_bz" label="说明" align="center" width="200">
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_bz"
size="small"
@click.stop
/>
<span v-else>{{ row.b_bz }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_groupby"
label="组"
align="center"
width="150"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_groupby"
size="small"
@click.stop
/>
<span v-else>{{ row.b_groupby || "未分组" }}</span>
</template>
</el-table-column>
<el-table-column width="170" fixed="right" align="center">
<template #header>
<span class="action-label">操作</span>
</template>
<template #default="{ row, $index }">
<div class="table-actions">
<el-button
size="small"
type="primary"
@click="addRowAbove($index)"
class="action-btn"
>
添加
</el-button>
<el-button
size="small"
type="primary"
@click="toggleEdit(row)"
class="action-btn"
>
{{ row.isEditing ? "完成" : "编辑" }}
</el-button>
<el-button
size="small"
type="danger"
@click="deleteRow(row, $index)"
class="action-btn"
>
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" :loading="loading.save" @click="handleSave"
>确认</el-button
>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.dialog {
--el-dialog-margin-top: 5vh;
}
.content-wrapper {
display: flex;
height: 600px;
}
.group-list {
width: 200px;
border-right: 1px solid #ebeef5;
display: flex;
flex-direction: column;
}
.group-header {
padding: 6px 16px;
font-weight: bold;
border-bottom: 1px solid #ebeef5;
background-color: #f5f7fa;
text-align: center;
}
.group-scroll {
flex: 1;
padding: 8px 0;
}
.group-item {
padding: 8px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.group-item:hover {
background-color: #f5f7fa;
}
.group-item.active {
background-color: #ecf5ff;
color: #409eff;
}
.count-badge {
background-color: #f0f0f0;
border-radius: 10px;
padding: 0 6px;
font-size: 12px;
}
.table-container {
flex: 1;
overflow: hidden;
}
.table {
font-size: 12px;
height: 100%;
}
.table ::v-deep(.el-table__header-wrapper th) {
background-color: #f5f7fa !important;
color: #303133 !important;
}
.table ::v-deep(.cell) {
line-height: normal;
padding: 0 4px;
}
.table ::v-deep(.el-table__cell) {
padding: 4px 0 !important;
}
.table-actions {
display: flex;
gap: 4px;
justify-content: center;
}
.action-btn {
padding: 5px 8px;
height: 24px;
font-size: 12px;
}
.action-label {
font-size: 12px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
padding-top: 16px;
}
</style>
@@ -0,0 +1,560 @@
<script setup>
import { loadDataApi, saveObjectApi } from "@/request/api";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({
modelValue: Boolean,
data: {
type: Object,
required: true,
default: () => ({}),
},
});
const loading = reactive({
table: true,
save: false,
});
const emit = defineEmits(["update:modelValue"]);
const visible = ref(props.modelValue);
const tableData = ref([]);
const selectedRows = ref([]);
const deletedRows = ref([]);
const activeGroup = ref(null);
watch(
() => props.modelValue,
(v) => (visible.value = v),
);
watch(visible, (v) => emit("update:modelValue", v));
// 计算属性:获取所有不重复的组名
const groupList = computed(() => {
const groups = new Set();
tableData.value.forEach((row) => {
groups.add(row.b_groupby || "");
});
return Array.from(groups).sort();
});
// 计算属性:根据当前选中的组过滤数据
const filteredTableData = computed(() => {
if (activeGroup.value === null || activeGroup.value === undefined) {
return tableData.value; // 显示所有数据
}
return tableData.value.filter((row) => {
const rowGroup = row.b_groupby || "";
return rowGroup === (activeGroup.value === "" ? null : activeGroup.value);
});
});
// 获取某个组的记录数量
const getGroupCount = (group) => {
return tableData.value.filter(
(row) => (row.b_groupby || "") === (group === "" ? null : group),
).length;
};
const handleSelectionChange = (rows) => {
selectedRows.value = rows;
};
// 显示所有组的数据
const showAllGroups = () => {
activeGroup.value = null;
};
// 按组过滤数据
const filterByGroup = (group) => {
activeGroup.value = group === activeGroup.value ? null : group;
};
const handleSwitchChange = (row) => {
if (!row.isNew) {
row.isModified = true;
}
};
async function initData() {
loading.table = true;
try {
const response = await loadDataApi("b_other_bmfl");
if (response.success) {
tableData.value = (response.data || []).map((item) => ({
...item,
b_canuse: Number(item.b_canuse) === 1 ? 1 : 0,
isEditing: false,
isModified: false,
isNew: false,
}));
console.log((await response).data)
deletedRows.value = [];
activeGroup.value = null; // 初始状态显示所有数据
} else {
tableData.value = [];
}
} catch (err) {
console.error("加载数据出错:", err);
tableData.value = [];
} finally {
loading.table = false;
}
}
const close = (resetData = true) => {
if (resetData) {
tableData.value = [];
selectedRows.value = [];
deletedRows.value = [];
activeGroup.value = null;
}
visible.value = false;
};
const handleSave = async () => {
// 验证所有修改过的行
const invalidRows = tableData.value.filter(
(row) =>
(row.isModified || row.isNew) && (!row.b_id?.trim() || !row.mc_b?.trim()),
);
if (invalidRows.length > 0) {
ElMessage.error("请填写完整的键和值");
return;
}
loading.save = true;
try {
// 构建保存数据
const updates = tableData.value
.filter((row) => row.isModified && !row.isNew)
.map((row) => ({
b_id: row.b_id,
mc_b: row.mc_b,
b_groupby: row.b_groupby,
b_tablename: row.b_tablename,
b_canuse: row.b_canuse,
b_xh: row.b_xh,
}));
const inserts = tableData.value
.filter((row) => row.isNew)
.map((row) => ({
b_id: row.b_id,
mc_b: row.mc_b,
b_groupby: row.b_groupby,
b_tablename: row.b_tablename,
b_canuse: row.b_canuse,
b_xh: row.b_xh,
}));
const deletes = deletedRows.value.map((row) => ({
b_id: row.b_id,
}));
const requestData = [
{
tableName: "b_other_bmfl",
keyField: "b_id",
inserts,
updates,
deletes,
},
];
const response = await saveObjectApi(requestData)
if (response.success) {
ElMessage.success("保存成功");
// 重置修改状态
tableData.value.forEach((row) => {
if (row.isNew) row.isNew = false;
if (row.isModified) row.isModified = false;
});
deletedRows.value = [];
// 重新加载数据
await initData();
} else {
throw new Error(response.data?.message || "保存失败");
}
} catch (err) {
console.error("保存出错:", err);
} finally {
loading.save = false;
}
};
function addRowAbove(index) {
const newRow = {
b_id: "",
mc_b: "",
b_groupby:
activeGroup.value === ""
? null
: activeGroup.value || props.data?.b_groupby || "",
b_tablename: "",
b_canuse: 1,
b_xh: 0,
isEditing: true,
isModified: true,
isNew: true,
};
tableData.value.splice(index, 0, newRow);
nextTick(() => {
const input = document.querySelector(
`.el-table__body tr:nth-child(${index + 1}) .el-input__inner`,
);
input?.focus();
});
}
function toggleEdit(row) {
row.isEditing = !row.isEditing;
if (row.isEditing && !row.isNew) {
row.isModified = true;
}
nextTick(() => {
const input = document.querySelector(".el-table__body .el-input__inner");
input?.focus();
});
}
const deleteRow = (row, index) => {
ElMessageBox.confirm("确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
if (!row.isNew) {
deletedRows.value.push(row);
}
tableData.value.splice(index, 1);
ElMessage.success("删除成功");
});
};
function validateField(row, field) {
if (field === "b_id" && !row.b_id.trim()) {
ElMessage.warning("键不能为空");
return false;
}
if (field === "mc_b" && !row.mc_b.trim()) {
ElMessage.warning("值不能为空");
return false;
}
return true;
}
</script>
<template>
<el-dialog
v-bind="$attrs"
v-model="visible"
title="基础数据目录"
width="1200px"
class="dialog"
@opened="initData"
>
<div class="content-wrapper">
<!-- 左侧组列表 -->
<div class="group-list">
<div
class="group-header"
:class="{ 'all-groups-active': activeGroup === null }"
@click="showAllGroups"
>
组列表
<el-tooltip effect="dark" content="点击显示所有数据" placement="top">
<el-icon><G3Icon icon="lucide:info" /></el-icon>
</el-tooltip>
</div>
<el-scrollbar class="group-scroll">
<div
v-for="group in groupList"
:key="group"
class="group-item"
:class="{ active: activeGroup === group }"
@click="filterByGroup(group)"
>
{{ group || "未分组" }}
<span class="count-badge">{{ getGroupCount(group) }}</span>
</div>
</el-scrollbar>
</div>
<!-- 右侧表格 -->
<div class="table-container">
<el-table
v-loading="loading.table"
:data="filteredTableData"
border
class="table"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" align="center" fixed />
<el-table-column prop="b_id" label="ID" align="center" width="150">
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_id"
size="small"
@blur="validateField(row, 'b_id')"
@click.stop
/>
<span v-else>{{ row.b_id }}</span>
</template>
</el-table-column>
<el-table-column prop="mc_b" label="名称" align="center" width="150">
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.mc_b"
size="small"
@blur="validateField(row, 'mc_b')"
@click.stop
/>
<span v-else>{{ row.mc_b }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_tablename"
label="表名"
align="center"
width="180"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_tablename"
size="small"
@click.stop
/>
<span v-else>{{ row.b_tablename }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_groupby"
label="分组"
align="center"
width="120"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_groupby"
size="small"
@click.stop
/>
<span v-else>{{ row.b_groupby || "未分组" }}</span>
</template>
</el-table-column>
<el-table-column prop="b_xh" label="排序" align="center" width="60">
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_xh"
size="small"
@click.stop
/>
<span v-else>{{ row.b_xh }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_canuse"
label="启用"
align="center"
width="100"
>
<template #default="{ row }">
<el-switch
v-model="row.b_canuse"
:active-value="1"
:inactive-value="0"
@change="handleSwitchChange(row)"
@click.stop
/>
</template>
</el-table-column>
<el-table-column width="170" fixed="right" align="center">
<template #header>
<span class="action-label">操作</span>
</template>
<template #default="{ row, $index }">
<div class="table-actions">
<el-button
size="small"
type="primary"
@click="addRowAbove($index)"
class="action-btn"
>
添加
</el-button>
<el-button
size="small"
type="primary"
@click="toggleEdit(row)"
class="action-btn"
>
{{ row.isEditing ? "完成" : "编辑" }}
</el-button>
<el-button
size="small"
type="danger"
@click="deleteRow(row, $index)"
class="action-btn"
>
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" :loading="loading.save" @click="handleSave"
>确认</el-button
>
</div>
</template>
</el-dialog>
</template>
<style scoped>
.dialog {
--el-dialog-margin-top: 5vh;
}
.content-wrapper {
display: flex;
height: 600px;
}
.group-list {
width: 200px;
border-right: 1px solid #ebeef5;
display: flex;
flex-direction: column;
}
.group-header {
padding: 6px 16px;
font-weight: bold;
border-bottom: 1px solid #ebeef5;
background-color: #f5f7fa;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.group-header:hover {
background-color: #e6e9ed;
}
.group-header.all-groups-active {
background-color: #ecf5ff;
color: #409eff;
}
.group-scroll {
flex: 1;
padding: 8px 0;
}
.group-item {
padding: 8px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.group-item:hover {
background-color: #f5f7fa;
}
.group-item.active {
background-color: #ecf5ff;
color: #409eff;
}
.count-badge {
background-color: #f0f0f0;
border-radius: 10px;
padding: 0 6px;
font-size: 12px;
}
.table-container {
flex: 1;
overflow: hidden;
}
.table {
font-size: 12px;
height: 100%;
}
.table ::v-deep(.el-table__header-wrapper th) {
background-color: #f5f7fa !important;
color: #303133 !important;
}
.table ::v-deep(.cell) {
line-height: normal;
padding: 0 4px;
}
.table ::v-deep(.el-table__cell) {
padding: 4px 0 !important;
}
.table-actions {
display: flex;
gap: 2px;
justify-content: center;
}
.action-btn {
padding: 5px 8px;
height: 24px;
font-size: 12px;
}
.action-label {
font-size: 12px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
padding-top: 16px;
}
.el-switch {
margin-left: 0;
}
.el-icon {
color: #909399;
}
</style>
@@ -0,0 +1,899 @@
<script setup>
import { loadDataBySqlApi, saveObjectApi } from "@/request/api";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({
modelValue: Boolean,
data: {
type: Object,
required: true,
default: () => ({}),
},
});
const loading = reactive({
table: true,
save: false,
qxtable: false,
Powertable: false,
current_moduleid: null,
});
const emit = defineEmits(["update:modelValue"]);
const visible = ref(props.modelValue);
const tableData = ref([]);
const selectedRows = ref([]);
const deletedRows = ref([]);
const activeGroup = ref(null);
const qxData = ref([]);
watch(
() => props.modelValue,
(v) => (visible.value = v),
);
watch(visible, (v) => emit("update:modelValue", v));
const currentEditRow = ref(null);
const handleSelectionChange = (rows) => {
selectedRows.value = rows;
};
const handlemoduleClick = (row, column, event) => {
console.log("handlemoduleClick");
console.log(row.b_id);
loading.current_moduleid = row.b_id;
LoadpowerData(row.b_id);
};
const handleRowClick = (row, column, event) => {
// 如果点击的是当前正在编辑的行,则不处理
if (currentEditRow.value === row.id) {
return;
}
// 关闭之前行的编辑状态
if (currentEditRow.value !== null) {
const prevRow = qxData.value.find(
(item) => item.id === currentEditRow.value,
);
if (prevRow) {
console.log(prevRow);
const oldrow = prevRow.originalData;
// 检查数据是否有变动
prevRow.isModified = isDataModified(prevRow, oldrow);
prevRow.isEditing = false;
}
}
// 设置新行的编辑状态
row.isEditing = true;
row.isModified = true;
currentEditRow.value = row.id;
};
// 简单的对象比较函数
const isDataModified = (obj1, obj2) => {
// 排除不需要比较的字段
const excludeFields = ["isEditing", "isModified"];
const keys1 = Object.keys(obj1).filter((key) => !excludeFields.includes(key));
const keys2 = Object.keys(obj2).filter((key) => !excludeFields.includes(key));
if (keys1.length !== keys2.length) return true;
for (let key of keys2) {
if (typeof obj1[key] === "object" && typeof obj2[key] === "object") {
// 如果是对象,递归比较
if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
return true;
}
} else if (obj1[key] !== obj2[key]) {
return true;
}
}
return false;
};
// 新增记录
const addNewRow = () => {
// 如果当前有编辑中的行,先处理
if (currentEditRow.value !== null) {
const prevRow = qxData.value.find(
(item) => item.id === currentEditRow.value,
);
if (prevRow) {
// handleRowChange(prevRow)
prevRow.isEditing = false;
}
}
// 计算新的排序号(最大值+1)
const maxSort =
qxData.value.length > 0
? Math.max(...qxData.value.map((item) => item.b_xh || 0))
: 0;
const newRow = {
id: generateId(),
b_xh: maxSort + 1,
ModuleID: loading.current_moduleid,
b_ivalue: "",
b_caption: "",
b_des: "",
isEditing: true, // 新增行默认进入编辑状态
isModified: false,
isNew: true, // 标记为新增行
isSaving: false, // 保存状态
};
newRow.originalData = { ...newRow };
// 添加到表格开头
qxData.value.push(newRow);
// 设置当前编辑行
currentEditRow.value = newRow.id;
// 滚动到顶部
nextTick(() => {
const tableWrapper = document.querySelector(".el-table__body-wrapper");
if (tableWrapper) {
tableWrapper.scrollTop = 0;
}
});
console.log("新增记录:", newRow);
};
// 生成唯一ID
let idCounter = 2;
const generateId = () => {
return ++idCounter;
};
async function initData() {
loading.table = true;
try {
Load_moduleData();
Load_powerData();
} catch (err) {
tableData.value = [];
} finally {
loading.table = false;
}
}
const moduleData = ref([]);
async function Load_moduleData() {
try {
moduleData.value = [];
let sql = `
select b_id,b_ChineseName from s_module where b_isMainModule='1'
order by b_order
`;
console.log("原始sql:", sql);
const response = await loadDataBySqlApi(sql);
if (response.success) {
const records = response.data || [];
// 转换数据
moduleData.value = records.map((item, index) => {
return {
id: item.id || `temp_${Date.now()}_${index}`,
b_id: item.b_id,
b_ChineseName: item.b_ChineseName,
};
});
}
} catch (err) {
moduleData.value = [];
} finally {
}
}
const formPowerData = ref([]);
async function Load_powerData() {
try {
formPowerData.value = [];
let sql = `
select b_ivalue,max(b_caption) b_caption,max(b_des) b_des from s_module_power
group by b_ivalue order by b_ivalue
`;
console.log("原始sql:", sql);
const response = await loadDataBySqlApi(sql);
if (response.success) {
const records = response.data || [];
// 转换数据
formPowerData.value = records.map((item, index) => {
return {
id: item.id || `temp_${Date.now()}_${index}`,
b_ivalue: item.b_ivalue || "0",
b_caption: item.b_caption || "",
b_des: item.b_des || "",
isEditing: false,
isModified: false,
isNew: false,
originalData: { ...item },
};
});
}
} catch (err) {
formPowerData.value = [];
} finally {
}
}
async function gethispower() {
try {
const selectedData = selectedRows.value;
if (selectedData.length === 0) {
// ElMessage.success('请先勾选记录')
ElMessage.error("请先勾选记录");
return;
}
var bxh = qxData.value.length;
selectedData.forEach((item, index) => {
bxh++;
const newRecord = {
ModuleID: loading.current_moduleid,
b_xh: bxh, // 默认为空
b_ivalue: item.b_ivalue,
b_caption: item.b_caption,
b_des: item.b_des,
// isEditing: true, // 添加时直接进入编辑状态
isNew: true, // 标记为新记录
originalData: { ...item },
};
qxData.value.push(newRecord);
});
} catch (err) {
qxData.value = [];
ElMessage.error(err?.message || "加载列信息失败");
} finally {
}
}
async function LoadpowerData(moduleid) {
loading.qxtable = true;
try {
let sql =
`
SELECT * FROM s_module_power
where ModuleID = '` +
moduleid +
`'
ORDER BY b_xh
`;
console.log("原始sql:", sql);
const response = await loadDataBySqlApi(sql);
if (response.success) {
const records = response.data || [];
// 转换数据
qxData.value = records.map((item, index) => {
// console.log(`处理第 ${index} 条数据:`, item)
return {
id: item.id || `temp_${Date.now()}_${index}`,
b_id: item.b_id || "",
b_xh: item.b_xh || "",
ModuleID: item.ModuleID || "",
b_ivalue: item.b_ivalue || "0",
b_caption: item.b_caption || "",
b_des: item.b_des || "",
isEditing: false,
isModified: false,
isNew: false,
originalData: { ...item },
};
});
}
} catch (err) {
qxData.value = [];
} finally {
loading.qxtable = false;
}
}
const close = (resetData = true) => {
if (resetData) {
tableData.value = [];
selectedRows.value = [];
deletedRows.value = [];
activeGroup.value = null;
}
visible.value = false;
};
const handleSave = async () => {
// 验证所有修改过的行
const invalidRows = qxData.value.filter((row) => row.isModified || row.isNew);
/*
if (invalidRows.length > 0) {
ElMessage.error('请填写完整的模块和Tag值')
return
}
*/
loading.save = true;
// 构建保存数据
const updates = qxData.value
.filter((row) => row.isModified && !row.isNew)
.map((row) => ({
b_id: row.b_id,
ModuleID: row.ModuleID,
b_ivalue: row.b_ivalue,
b_xh: row.b_xh,
b_caption: row.b_caption,
b_des: row.b_des,
}));
const inserts = qxData.value
.filter((row) => row.isNew)
.map((row) => ({
b_id: row.b_id,
ModuleID: row.ModuleID,
b_ivalue: row.b_ivalue,
b_xh: row.b_xh,
b_caption: row.b_caption,
b_des: row.b_des,
}));
const deletes = deletedRows.value.map((row) => ({
b_id: row.b_id,
}));
const requestData = [
{
tableName: "s_module_power",
keyField: "b_id",
inserts,
updates,
deletes,
},
];
try {
const response = await saveObjectApi(requestData);
if (response.data?.success) {
ElMessage.success("保存成功");
await initData(); // 重新加载数据,这会更新组列表
} else {
throw new Error(response.data?.message || "保存失败");
}
} catch (err) {
console.error("保存出错:", err);
ElMessage.error(err.message || "保存失败");
} finally {
loading.save = false;
}
};
/*
const deleteRow = (row, index) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
if (!row.isNew) {
deletedRows.value.push(row) // 记录已删除的行
}
qxData.value.splice(index, 1)
ElMessage.success('删除成功')
})
} */
const deleteRow = (row) => {
ElMessageBox.confirm("确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
if (!row.isNew) {
deletedRows.value.push(row);
}
// 通过 findIndex 查找当前行的索引
const index = qxData.value.findIndex((item) => item.id === row.id); // 假设有唯一标识如 id
if (index !== -1) {
qxData.value.splice(index, 1);
ElMessage.success("删除成功");
}
});
};
//////////===== 获取 SQL语句预览 -------------
const dialogSQLVisible = ref(false);
const dialogTitle = ref("SQL语句预览");
const sqlContent = ref("");
const showqxSql = (row) => {
dialogSQLVisible.value = true;
try {
let sql =
`delete from s_module_power where ModuleID=' ` +
loading.current_moduleid +
`';\n\n`;
// 使用 reduce 或 forEach 拼接
qxData.value.forEach((item, index) => {
// 建议添加数据校验
if (item.ModuleID && item.b_ivalue !== undefined) {
const insertSql =
`insert into s_module_power(ModuleID,b_ivalue,b_xh,b_caption,b_des)\n` +
`values('${item.ModuleID}','${item.b_ivalue}','${item.b_xh || ""}','${item.b_caption || ""}','${item.b_des || ""}');`;
sql += insertSql + "\n";
}
});
sqlContent.value = sql;
} catch (err) {
} finally {
}
};
const handleDialogClose = () => {
dialogSQLVisible.value = false;
sqlContent.value = ""; // 清空内容
};
// 复制SQL到剪贴板
const handleCopySql = async () => {
try {
await navigator.clipboard.writeText(sqlContent.value);
ElMessage.success("SQL已复制到剪贴板");
} catch (err) {
// 降级方案
const textArea = document.createElement("textarea");
textArea.value = sqlContent.value;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand("copy");
ElMessage.success("SQL已复制到剪贴板");
} catch (e) {
ElMessage.error("复制失败");
}
document.body.removeChild(textArea);
}
};
//////////===== 获取 SQL语句预览 ------结束-------
</script>
<template>
<el-dialog
v-bind="$attrs"
v-model="visible"
title="模块-权限设置"
width="1200px"
class="dialog"
@open="initData"
>
<div class="content-wrapper">
<!-- 左侧组列表 -->
<div class="g3-container-left">
<el-table
v-loading="loading.moduletable"
:data="moduleData"
border
class="table"
height="560px"
:max-height="tableHeight"
@row-click="handlemoduleClick"
>
<el-table-column
prop="b_id"
label="模块ID"
align="center"
width="150"
>
<template #default="{ row }">
<el-input v-if="row.isEditing" v-model="row.b_id" size="small" />
<span v-else>{{ row.b_id }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_ChineseName"
label="模块名称"
align="center"
width="120"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_ChineseName"
size="small"
/>
<span v-else>{{ row.b_ChineseName }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="table-container">
<div class="g3-row-main">
<div style="width: 100%; margin-bottom: 4px">
<div style="display: flex">
<el-button
type="primary"
:loading="loading.save"
@click="addNewRow"
>新增</el-button
>
<el-button type="primary" @click="handleSave">保存</el-button>
<el-button type="primary" @click="showqxSql">SQL语句</el-button>
</div>
</div>
<div
style="
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
"
>
<el-table
v-loading="loading.qxtable"
:data="qxData"
border
class="table"
height="560px"
:max-height="tableHeight"
@row-click="handleRowClick"
>
<el-table-column
prop="b_xh"
label="排序"
align="center"
width="50"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_xh"
size="small"
/>
<span v-else>{{ row.b_xh }}</span>
</template>
</el-table-column>
<el-table-column
v-if="false"
prop="b_id"
label="流水号"
align="center"
width="0"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_id"
size="small"
/>
<span v-else>{{ row.b_id }}</span>
</template>
</el-table-column>
<el-table-column
prop="ModuleID"
label="对应模块"
align="center"
width="120"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.ModuleID"
size="small"
/>
<span v-else>{{ row.ModuleID }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_ivalue"
label="Tag值"
align="center"
width="60"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_ivalue"
size="small"
@blur="validateField(row, 'b_ivalue')"
@click.stop
/>
<span v-else>{{ row.b_ivalue }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_caption"
label="Caption"
align="center"
width="130"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_caption"
size="small"
@blur="validateField(row, 'b_caption')"
@click.stop
/>
<span v-else>{{ row.b_caption }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_des"
label="描述"
align="center"
width="170"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_des"
size="small"
@blur="validateField(row, 'b_des')"
@click.stop
/>
<span v-else>{{ row.b_des }}</span>
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
align="center"
width="100"
>
<template #default="{ row, $index }">
<el-button
:disabled="false"
type="primary"
link
@click="deleteRow(row, $index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<div class="group-right">
<div class="g3-row-main">
<div style="width: 100%; margin-bottom: 4px">
<div style="display: flex">
<el-button
type="primary"
:loading="loading.save"
@click="gethispower"
>←导</el-button
>
</div>
</div>
<div
style="
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
"
>
<el-table
v-loading="loading.powertable"
:data="formPowerData"
border
class="table"
height="560px"
:max-height="tableHeight"
ref="powerTableRef"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="40"
align="center"
fixed
/>
<el-table-column
prop="b_ivalue"
label="Tag值"
align="center"
width="60"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_ivalue"
size="small"
/>
<span v-else>{{ row.b_ivalue }}</span>
</template>
</el-table-column>
<el-table-column
prop="b_caption"
label="Caption"
align="center"
width="100"
>
<template #default="{ row }">
<el-input
v-if="row.isEditing"
v-model="row.b_caption"
size="small"
/>
<span v-else>{{ row.b_caption }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<!-- 对话框内容 -->
<el-dialog
v-model="dialogSQLVisible"
:title="dialogTitle"
width="650px"
:close-on-click-modal="false"
>
<div class="sql-preview-container">
<el-input
v-model="sqlContent"
type="textarea"
:autosize="{ minRows: 8, maxRows: 20 }"
placeholder="SQL语句将在此处显示..."
readonly
class="sql-textarea"
>
</el-input>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleDialogClose">关闭</el-button>
<el-button type="primary" @click="handleCopySql">复制SQL</el-button>
</div>
</template>
</el-dialog>
</div>
</el-dialog>
</template>
<style scoped>
.dialog {
--el-dialog-margin-top: 5vh;
}
.content-wrapper {
display: flex;
height: 600px;
}
.group-list {
width: 160px;
border-right: 1px solid #ebeef5;
display: flex;
flex-direction: column;
}
.group-header {
padding: 6px 16px;
font-weight: bold;
border-bottom: 1px solid #ebeef5;
background-color: #f5f7fa;
text-align: center;
}
.group-scroll {
flex: 1;
padding: 8px 0;
}
.group-item {
padding: 8px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.group-item:hover {
background-color: #f5f7fa;
}
.group-item.active {
background-color: #ecf5ff;
color: #409eff;
}
.count-badge {
background-color: #f0f0f0;
border-radius: 10px;
padding: 0 6px;
font-size: 12px;
}
.g3-container-left {
width: 190px;
min-width: 170px;
margin-right: 10px;
background-color: #ffffff;
border-right: 1px solid #e4e7ed;
border-radius: 6px;
box-shadow: 0 0 12px #0000000d;
padding: 10px;
overflow-y: auto;
position: relative;
width: 300px;
}
.table-container {
width: 260px;
flex: 1;
overflow: hidden;
}
.table {
font-size: 12px;
height: 100%;
}
.table ::v-deep(.el-table__header-wrapper th) {
background-color: #f5f7fa !important;
color: #303133 !important;
}
.table ::v-deep(.cell) {
line-height: normal;
padding: 0 4px;
}
.table ::v-deep(.el-table__cell) {
padding: 4px 0 !important;
}
.table-actions {
display: flex;
gap: 4px;
justify-content: center;
}
.group-right {
width: 220px;
border-right: 1px solid #ebeef5;
display: flex;
flex-direction: column;
}
.action-btn {
padding: 5px 8px;
height: 24px;
font-size: 12px;
}
.action-label {
font-size: 12px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
padding-top: 16px;
}
/* 自定义选中行样式 */
.table :deep(.selected-row) {
background-color: #f0f9ff !important;
}
/* Element Plus 自带的高亮行样式 */
.table :deep(.el-table__row.current-row) {
background-color: #f0f9ff !important;
}
.table :deep(.el-table__row:hover) {
background-color: #f5f7fa !important;
}
/* 高亮当前节点背景色 */
.el-tree-node.is-current > .el-tree-node__content {
background-color: #e6f7ff !important;
color: #1890ff !important;
}
</style>
@@ -0,0 +1,88 @@
<script setup>
import { ref } from "vue";
const props = defineProps({
data: {
type: Array,
required: true,
},
viewtable: {
type: String,
required: true,
},
b_id: {
// 确保 b_id 在 props 里定义
type: String,
required: true,
},
});
const sqlStr = ref("");
function initData() {
console.log(props.b_id);
sqlStr.value = "SQL语句:";
// if (!props.viewtable) return
const vrows = props.data || [];
const sqlArr = [];
// 删除语句
const deleteSql = `DELETE FROM s_module WHERE b_id='${props.b_id}';`;
sqlArr.push(deleteSql);
console.log(deleteSql);
// 插入语句
vrows.forEach((row) => {
const insertSql = `
INSERT INTO s_module(
b_id, b_ChineseName, b_ViewTable, b_SaveTable, b_JudgeCondition,
b_Message, b_BMFL, b_Order, b_ISVirtualModule, b_LinkTable,
b_DefaultCondition, b_OrderField, b_SH, b_Cate_ID, b_autoid,
b_searchsql, b_haveownerid
)
VALUES(
'${props.b_id}', '${row.b_ChineseName}', '${row.b_ViewTable}', '${row.b_SaveTable}', '${row.b_JudgeCondition}',
'${row.b_Message}','${row.b_BMFL}', '${row.b_Order}', '${row.b_ISVirtualModule}','${row.b_LinkTable}',
'${row.b_DefaultCondition}', '${row.b_OrderField}','${row.b_SH}', '${row.b_Cate_ID}', '${row.b_autoid}',
'${row.b_searchsql}', '${row.b_haveownerid}'
);
`;
sqlArr.push(insertSql);
});
// 更新 sqlStr(注意:使用 .value 而不是 this)
sqlStr.value = sqlArr.join("\n");
// console.log(sqlArr.join('\n'));
}
</script>
<template>
<el-dialog
v-bind="$attrs"
title="获取SQL语句"
width="1000px"
class="dialog"
@open="initData"
>
<div class="sql-preview">
{{ sqlStr }}
</div>
</el-dialog>
</template>
<style scoped>
.sql-preview {
width: 100%;
height: 500px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
font-family: monospace;
padding: 8px;
border: 1px solid #e0e0e0;
background-color: #fafafa;
box-sizing: border-box;
}
</style>
@@ -0,0 +1,7 @@
<script lang="js" setup></script>
<template>
<div class=""></div>
</template>
<style lang="scss" scoped></style>
@@ -0,0 +1,208 @@
<script lang="js" setup>
import { loadObjectApi, saveObjectApi } from "@/request/api";
import { diffObject } from "@/utils/diff";
import { ElMessage } from "element-plus";
const props = defineProps({
// 员工ID,用于获取数据
employeeId: {
type: [String, Number],
default: "",
},
employeeName: {
type: [String],
default: "",
},
});
const showDialog = defineModel(false);
const isNew = ref(true);
const maintableOld = ref({});
const maintable = ref({});
const loading = ref(false);
function onOpened() {
loadFormData();
}
function loadFormData() {
loading.value = true;
if (!props.employeeId) {
ElMessage.warning("加载失败");
showDialog.value = false;
}
loadObjectApi({
viewName: "v_b_employee",
keyField: "b_id",
keyValue: props.employeeId,
})
.then((res) => {
if (res.success) {
if (res.data) {
maintable.value = res.data;
maintableOld.value = structuredClone(maintable.value);
isNew.value = false;
} else {
maintable.value.b_id = props.employeeId;
maintable.value.b_name_ch = props.employeeName || "";
maintableOld.value = structuredClone(maintable.value);
isNew.value = true;
}
}
})
.finally(() => {
loading.value = false;
});
}
function handleConfirm() {
let obj = {};
if (isNew.value) {
maintable.value.b_customer_id = "b_employee";
maintable.value.b_state = "草拟";
obj = maintable.value;
} else {
const diffResult = diffObject(
maintable.value,
maintableOld.value,
["b_id"],
[],
);
if (!diffResult.isChanged) {
ElMessage.success("保存成功");
loadFormData();
return;
}
obj = diffResult.result;
}
const requestData = [
{
tableName: "b_contact",
keyField: "b_id",
inserts: isNew.value ? [obj] : [],
updates: isNew.value ? [] : [obj],
deletes: [],
},
];
loading.value = true;
saveObjectApi(requestData)
.then((res) => {
if (res.success) {
ElMessage.success("保存成功");
}
})
.finally(() => {
loading.value = false;
});
}
function resetForm() {
maintable.value = {};
maintableOld.value = {};
loading.value = true;
isNew.value = true;
}
</script>
<template>
<el-dialog
title="员工资料"
width="400"
v-model="showDialog"
@closed="resetForm"
@opened="onOpened"
>
<div style="width: 100%" v-loading="loading">
<G3Grid>
<G3GridGroup :label-width="80" :column="1" label-align="right">
<G3GridItem label="账号" :span="1" required>
<el-input v-model="maintable.b_id" disabled />
</G3GridItem>
<G3GridItem label="姓名" :span="1">
<el-input v-model="maintable.b_name_ch" />
</G3GridItem>
<G3GridItem label="手机电话" :span="1">
<el-input v-model="maintable.b_phone" />
</G3GridItem>
<G3GridItem label="QQ" :span="1">
<el-input v-model="maintable.b_fax" />
</G3GridItem>
<G3GridItem label="邮箱" :span="1">
<el-input v-model="maintable.b_email" />
</G3GridItem>
<G3GridItem label="性别" :span="1">
<el-select v-model="maintable.b_sex">
<el-option label="男" value="男" />
<el-option label="女" value="女" />
</el-select>
</G3GridItem>
<G3GridItem label="部门" :span="1">
<el-input v-model="maintable.b_departmentname" />
</G3GridItem>
<G3GridItem label="职位" :span="1">
<el-input v-model="maintable.b_duty" />
</G3GridItem>
<G3GridItem label="合同开始" :span="1">
<el-date-picker
v-model="maintable.b_htbegindate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</G3GridItem>
<G3GridItem label="合同到期" :span="1">
<el-date-picker
v-model="maintable.b_htenddate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</G3GridItem>
<G3GridItem label="紧急电话" :span="1">
<el-input v-model="maintable.b_homephone" />
</G3GridItem>
<G3GridItem label="详细地址" :span="1">
<el-input v-model="maintable.b_homeaddr" type="textarea" />
</G3GridItem>
<G3GridItem label="备注" :span="1">
<el-input v-model="maintable.b_bz" type="textarea" />
</G3GridItem>
</G3GridGroup>
</G3Grid>
<div class="footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleConfirm">
确认
</el-button>
</div>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.footer {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
}
</style>
@@ -4,6 +4,7 @@ import { useUserStore } from "@/store/user";
import { diffObject } from "@/utils/diff";
import PermissionModel from "./permission-model.vue";
import PermissionCopyModal from "./permission-copy-modal.vue";
import Employee from "./employee.vue";
const userStore = useUserStore();
@@ -38,6 +39,9 @@ const dialogData = ref({});
// 复制权限
const copyDialogVisible = ref(false);
// 员工资料
const showEmployee = ref(false);
onMounted(() => {
getTableData();
});
@@ -205,7 +209,9 @@ async function deleteRows(rows = []) {
return;
}
const selectedIds = rows.filter((row) => row.b_id !== "g3soft").map((row) => row.b_id);
const selectedIds = rows
.filter((row) => row.b_id !== "g3soft")
.map((row) => row.b_id);
const delectedIds = rows
.filter((row) => !$table.isInsertByRow(row) && row.b_id !== "g3soft")
@@ -274,7 +280,8 @@ async function handleSaveRow(row) {
// 1. 检查表格内重复(本地数据)
const duplicate = tableData.value.some(
(item) => item.b_id?.trim().toUpperCase() === editId && item.b_id !== row.b_id, // 排除自己
(item) =>
item.b_id?.trim().toUpperCase() === editId && item.b_id !== row.b_id, // 排除自己
);
if (duplicate) {
ElMessage.warning("账号重复,请修改");
@@ -385,7 +392,9 @@ function handleShowBatchCopyDialog() {
if ($table) {
const records = $table.getCheckboxRecords();
const filteredRecords = records.filter((item) => !$table.isInsertByRow(item));
const filteredRecords = records.filter(
(item) => !$table.isInsertByRow(item),
);
const bIds = filteredRecords.map((item) => item.b_id);
@@ -416,12 +425,26 @@ function handleShowBatchCopyDialog() {
</el-icon>
批量删除
</el-button>
<el-button type="primary" @click="showPasswordRow = !showPasswordRow">查看密码</el-button>
<el-button type="primary" @click="handleShowBatchCopyDialog">批量复制权限</el-button>
<el-button type="primary" @click="handleShowCopyDialog" :disabled="!isSelectedRow">
<el-button type="primary" @click="showPasswordRow = !showPasswordRow"
>查看密码</el-button
>
<el-button type="primary" @click="handleShowBatchCopyDialog"
>批量复制权限</el-button
>
<el-button
type="primary"
@click="handleShowCopyDialog"
:disabled="!isSelectedRow"
>
复制权限
</el-button>
<el-button type="primary" :disabled="!isSelectedRow">员工资料</el-button>
<el-button
type="primary"
:disabled="!isSelectedRow"
@click="showEmployee = true"
>
员工资料
</el-button>
</div>
<div class="header-right">
@@ -538,10 +561,18 @@ function handleShowBatchCopyDialog() {
}"
>
<template #default="{ row }">
<el-checkbox v-model="row.b_canuse" true-value="1" false-value="0" />
<el-checkbox
v-model="row.b_canuse"
true-value="1"
false-value="0"
/>
</template>
<template #edit="{ row }">
<el-checkbox v-model="row.b_canuse" true-value="1" false-value="0" />
<el-checkbox
v-model="row.b_canuse"
true-value="1"
false-value="0"
/>
</template>
</vxe-column>
<vxe-column
@@ -585,7 +616,12 @@ function handleShowBatchCopyDialog() {
>
保存
</el-button>
<el-button link @click="cancelEditRow(row)" @click.stop style="margin-left: 6px">
<el-button
link
@click="cancelEditRow(row)"
@click.stop
style="margin-left: 6px"
>
取消
</el-button>
</template>
@@ -639,6 +675,11 @@ function handleShowBatchCopyDialog() {
<PermissionModel v-model="dialogVisible" :data="dialogData" />
<PermissionCopyModal v-model="copyDialogVisible" :ids="selectedIds" />
<Employee
v-model="showEmployee"
:employee-id="selectCurrentRow?.b_id"
:employee-name="selectCurrentRow?.b_name"
/>
</div>
</template>
@@ -0,0 +1,180 @@
<script lang="js" setup>
import { loadObjectApi, saveObjectApi } from "@/request/api";
import { useUserStore } from "@/store/user";
import { diffObject } from "@/utils/diff";
import { ElMessage } from "element-plus";
const userStore = useUserStore();
const isNew = ref(true);
const maintableOld = ref({});
const maintable = ref({});
const loading = ref(false);
const userid = ref(userStore.userInfo.userid);
onMounted(() => {
loadFormData();
});
function loadFormData() {
loading.value = true;
loadObjectApi({
viewName: "v_b_employee",
keyField: "b_id",
keyValue: userid.value,
})
.then((res) => {
if (res.success) {
if (res.data) {
maintable.value = res.data;
maintableOld.value = structuredClone(maintable.value);
isNew.value = false;
} else {
maintable.value.b_id = userid.value;
maintable.value.b_name_ch = userStore.userInfo.username || "";
maintableOld.value = structuredClone(maintable.value);
isNew.value = true;
}
}
})
.finally(() => {
loading.value = false;
});
}
function handleConfirm() {
let obj = {};
if (isNew.value) {
maintable.value.b_customer_id = "b_employee";
maintable.value.b_state = "草拟";
obj = maintable.value;
} else {
const diffResult = diffObject(
maintable.value,
maintableOld.value,
["b_id"],
[],
);
if (!diffResult.isChanged) {
ElMessage.success("保存成功");
loadFormData();
return;
}
obj = diffResult.result;
}
const requestData = [
{
tableName: "b_contact",
keyField: "b_id",
inserts: isNew.value ? [obj] : [],
updates: isNew.value ? [] : [obj],
deletes: [],
},
];
loading.value = true;
saveObjectApi(requestData)
.then((res) => {
if (res.success) {
ElMessage.success("保存成功");
isNew.value = false;
loadFormData();
}
})
.finally(() => {
loading.value = false;
});
}
</script>
<template>
<div style="width: 360px" v-loading="loading">
<G3Grid>
<G3GridGroup :label-width="80" :column="1" label-align="right">
<G3GridItem label="账号" :span="1" required>
<el-input v-model="maintable.b_id" disabled />
</G3GridItem>
<G3GridItem label="姓名" :span="1">
<el-input v-model="maintable.b_name_ch" />
</G3GridItem>
<G3GridItem label="手机电话" :span="1">
<el-input v-model="maintable.b_phone" />
</G3GridItem>
<G3GridItem label="QQ" :span="1">
<el-input v-model="maintable.b_fax" />
</G3GridItem>
<G3GridItem label="邮箱" :span="1">
<el-input v-model="maintable.b_email" />
</G3GridItem>
<G3GridItem label="性别" :span="1">
<el-select v-model="maintable.b_sex">
<el-option label="男" value="男" />
<el-option label="女" value="女" />
</el-select>
</G3GridItem>
<G3GridItem label="部门" :span="1">
<el-input v-model="maintable.b_departmentname" />
</G3GridItem>
<G3GridItem label="职位" :span="1">
<el-input v-model="maintable.b_duty" />
</G3GridItem>
<G3GridItem label="合同开始" :span="1">
<el-date-picker
v-model="maintable.b_htbegindate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</G3GridItem>
<G3GridItem label="合同到期" :span="1">
<el-date-picker
v-model="maintable.b_htenddate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</G3GridItem>
<G3GridItem label="紧急电话" :span="1">
<el-input v-model="maintable.b_homephone" />
</G3GridItem>
<G3GridItem label="详细地址" :span="1">
<el-input v-model="maintable.b_homeaddr" type="textarea" />
</G3GridItem>
<G3GridItem label="备注" :span="1">
<el-input v-model="maintable.b_bz" type="textarea" />
</G3GridItem>
</G3GridGroup>
</G3Grid>
<div class="footer">
<el-button :loading="loading">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleConfirm">
确认
</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.footer {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
}
</style>
@@ -0,0 +1,25 @@
<script lang="js" setup>
import UpdatePassword from "./update-password.vue";
import Employee from "./employee.vue";
</script>
<template>
<el-tabs tab-position="left" class="tabs">
<el-tab-pane label="个人信息">
<Employee />
</el-tab-pane>
<el-tab-pane label="修改密码">
<UpdatePassword />
</el-tab-pane>
</el-tabs>
</template>
<style lang="scss" scoped>
.tabs {
height: 100%;
background-color: #fff;
:deep(.el-tabs__content) {
overflow-y: auto;
}
}
</style>
@@ -1,9 +1,8 @@
<script setup>
<script lang="js" setup>
import { updatePasswordApi } from "@/request/api";
import { ElMessage } from "element-plus";
import { ref } from "vue";
const loading = ref(false);
const confirmLoading = ref(false);
const form = ref({
oldPassword: "",
@@ -11,115 +10,75 @@ const form = ref({
confirmPassword: "",
});
function resetFrom() {
form.value.oldPassword = "";
form.value.newPassword = "";
form.value.confirmPassword = "";
}
async function handleConfirm() {
if (form.value.newPassword !== form.value.confirmPassword) {
function handleConfirm() {
if (form.value.newPassword === "") {
ElMessage.warning("新密码不能为空");
return;
}
if (form.value.confirmPassword !== form.value.newPassword) {
ElMessage.warning("新密码和确认密码不一致");
return;
}
loading.value = true;
confirmLoading.value = true;
const requestData = {
oldPassword: form.value.oldPassword,
updatePasswordApi({
newPassword: form.value.newPassword,
};
try {
const saveRes = await axios.post("/updatePassword", requestData, {
headers: { "Content-Type": "application/json" },
oldPassword: form.value.oldPassword,
})
.then((res) => {
if (res.success) {
// 修改成功
ElMessage.success("修改成功");
resetForm();
}
})
.finally(() => {
confirmLoading.value = false;
});
}
if (saveRes.data.success) {
ElMessage.success("密码更新成功");
resetFrom();
} else {
ElMessage.error(`密码更新失败:${saveRes.data.message}`);
}
} catch (error) {
ElMessage.error(`密码更新失败:${error.message || "未知错误"}`);
} finally {
loading.value = false;
}
function resetForm() {
form.value.newPassword = "";
form.value.oldPassword = "";
form.value.confirmPassword = "";
}
</script>
<template>
<div class="g3-container">
<div class="g3-grid">
<div class="g3-cell">
<div class="g3-cell-label">旧密码:</div>
<el-input v-model="form.oldPassword" type="password" />
</div>
<div class="g3-cell">
<div class="g3-cell-label">新密码:</div>
<div class="g3-cell-value">
<el-input v-model="form.newPassword" type="password" />
</div>
</div>
<div class="g3-cell">
<div class="g3-cell-label">确认密码:</div>
<el-input v-model="form.confirmPassword" type="password" />
</div>
</div>
<div class="g3-actions">
<el-button type="primary" :loading="loading" @click="handleConfirm"> 确认 </el-button>
<el-button :loading="loading" @click="resetFrom"> 重置 </el-button>
<div style="width: 360px">
<G3Grid>
<G3GridGroup :label-width="80" :column="1" label-align="right">
<G3GridItem label="旧密码" :span="1">
<el-input v-model="form.oldPassword" type="password" show-password />
</G3GridItem>
<G3GridItem label="新密码" :span="1" required>
<el-input v-model="form.newPassword" type="password" show-password />
</G3GridItem>
<G3GridItem label="确认密码" :span="1" required>
<el-input
v-model="form.confirmPassword"
type="password"
show-password
/>
</G3GridItem>
</G3GridGroup>
</G3Grid>
<div class="footer">
<el-button @click="resetForm">取消</el-button>
<el-button type="primary" :loading="confirmLoading" @click="handleConfirm"
>确认</el-button
>
</div>
</div>
</template>
<style lang="scss" scoped>
.g3-container {
padding: 16px;
width: 400px;
}
.g3-grid {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 1px;
border: 1px solid #ddd;
background-color: #ddd;
}
.g3-cell {
display: flex;
align-items: center;
color: #333;
font-size: 14px;
height: 32px;
}
.g3-cell-label {
padding: 8px;
border-right: 1px solid #ddd;
.footer {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
width: 95px;
flex-shrink: 0;
background: #f9f9f9;
height: 100%;
}
.g3-cell-value {
width: 100%;
}
.g3-actions {
display: flex;
margin-top: 12px;
justify-content: end;
}
:deep(.el-input__wrapper) {
border: none !important;
box-shadow: none !important;
border-radius: 0px;
}
</style>