This commit is contained in:
ylj20011123 2025-11-18 17:52:20 +08:00
parent 1cd8c40653
commit c23e630f6b
12 changed files with 419 additions and 41 deletions

View File

@ -588,6 +588,11 @@ export default [
name: 'OperationLog',
component: './Setting/OperationLog/index',
},
{
path: 'LoginLog',
name: 'LoginLog',
component: './Setting/LoginLog/index',
},
{
path: 'menu',
name: 'moduleCate',

BIN
dist.zip

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "ant-design-pro",
"version": "4.5.74",
"version": "4.5.77",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"scripts": {

View File

@ -10,12 +10,13 @@
import type { Reducer, Effect } from 'umi';
import { history } from 'umi';
import { accountLogin } from '@/services/login';
import { accountLogin, handleUpdateLoginLog } from '@/services/login';
import { setAuthority } from '@/utils/authority';
import { message } from 'antd';
import session from '@/utils/session';
import { wrapTreeNode } from '@/utils/format';
import moment from 'moment'
export type StateType = {
status?: 'ok' | 'error';
@ -51,6 +52,7 @@ const Model: LoginModelType = {
// 登录方法
* login({ payload }, { call, put }) {
const response = yield call(accountLogin, payload);
yield put({
type: 'changeLoginStatus',
payload: response,
@ -62,6 +64,20 @@ const Model: LoginModelType = {
message.success('🎉 🎉 🎉 登录成功!');
handleUpdateLoginLog({
OPERATELOG_TYPE: 1000,
USER_ID: response.currentUser.ID,
USER_NAME: response.currentUser.Name,
USER_PASSPORT: response.currentUser.UserName,
MODULE_NAME: "用户登录",
OPERATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss'),
OPERATELOG_DESC: response.currentUser.Name ? `${response.currentUser.Name}${moment().format('YYYY-MM-DD HH:mm:ss')}登录了系统` : "登录成功",
USER_LOGINIP: payload?.LoginIP || "",
USER_LOGINPLACE: payload?.LoginPlace || "",
BROWSER_VERSION: payload?.BrowserVersion || "",
OPERATING_SYSTEM: payload?.OperatingSystem || "",
})
if (!history) return;
const { query } = history.location;
const { redirect } = query as {
@ -111,7 +127,6 @@ const Model: LoginModelType = {
}
},
*logout(_, { put }) { // 退出登录
yield put({
type: 'global/changeTabsRoutes',
payload: { data: [], action: 'removeAll' },
@ -121,6 +136,21 @@ const Model: LoginModelType = {
const { query = {} } = history.location;
const { redirect } = query;
const currentUser = session.get('currentUser')
console.log('currentUsercurrentUsercurrentUser', currentUser);
handleUpdateLoginLog({
OPERATELOG_TYPE: 1002,
USER_ID: currentUser.ID,
USER_NAME: currentUser.Name,
USER_PASSPORT: currentUser.UserName,
MODULE_NAME: "用户登出",
OPERATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss'),
OPERATELOG_DESC: currentUser.Name ? `${currentUser.Name}${moment().format('YYYY-MM-DD HH:mm:ss')}登出了系统` : "登出成功",
})
// Note: There may be security issues, please note
if (window.location.pathname !== '/user/login' && !redirect) {
history.replace({

View File

@ -0,0 +1,286 @@
import { connect } from "umi";
import type { CurrentUser } from "umi";
import type { ConnectState } from "@/models/connect";
import React, { useRef, useState } from "react";
import type { FormInstance } from "antd";
import { Button, message, Space, Spin, Tree } from "antd";
import type { ActionType } from "@ant-design/pro-table";
import ProTable from "@ant-design/pro-table";
import ReactHTMLTableToExcel from "react-html-table-to-excel";
import { handleGetOPERATELOGList } from "./service";
import moment from 'moment'
const saleRankReport: React.FC<{ currentUser: CurrentUser }> = (props) => {
const { currentUser } = props
const downloadBtnRef = useRef<any>()
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const [collapsible, setCollapsible] = useState<boolean>(false)
const [treeView, setTreeView] = useState<any>()
const [printIndex, setPrintIndex] = useState<number>(new Date().getTime())
// 树相关的属性和方法
const [selectedId, setSelectedId] = useState<string>()
// 导出的加载效果
const [showLoading, setShowLoading] = useState<boolean>(false)
// 是否显示打印的表格
const [showExportTable, setShowExportTable] = useState<boolean>(false)
// 查询的条件
const [searchParams, setSearchParams] = useState<any>()
const columns: any = [
{
title: '查询内容',
width: 120,
ellipsis: true,
dataIndex: 'searchValue',
hideInTable: true,
},
{
title: '查询日期',
width: 160,
valueType: 'date',
hideInTable: true,
dataIndex: 'searchDate',
initialValue: moment(),
},
{
title: '访问编码',
width: 120,
ellipsis: true,
hideInSearch: true,
dataIndex: 'OPERATELOG_ID'
},
{
dataIndex: 'OPERATELOG_TYPE',
title: '登录状态',
width: 120,
align: 'center',
valueType: 'select',
hideInSearch: true,
valueEnum: {
1000: { text: '登录', status: 'success' },
1002: { text: '登出', status: 'error' },
}
},
{
dataIndex: 'USER_PASSPORT',
title: '登录账号',
width: 200,
ellipsis: true,
align: 'center',
hideInSearch: true,
},
{
dataIndex: 'USER_LOGINIP',
title: '登录地址',
ellipsis: true,
width: 160,
align: 'center',
hideInSearch: true,
},
{
dataIndex: 'USER_LOGINPLACE',
title: '登录地点',
ellipsis: true,
width: 160,
align: 'center',
hideInSearch: true,
},
{
dataIndex: 'BROWSER_VERSION',
title: '浏览器版本',
width: 140,
ellipsis: true,
align: 'center',
hideInSearch: true,
},
{
dataIndex: 'OPERATING_SYSTEM',
title: '操作系统',
width: 120,
align: 'center',
hideInSearch: true,
},
{
dataIndex: 'OPERATELOG_DESC',
title: <div style={{ textAlign: 'center' }}></div>,
ellipsis: true,
width: 260,
align: 'left',
hideInSearch: true,
},
{
dataIndex: 'OPERATE_DATE',
title: '登录时间',
ellipsis: true,
width: 160,
align: 'center',
hideInSearch: true,
},
]
const exportTable = (e) => {
e.stopPropagation(); // 防止Collapse组件收起
const main = document.getElementsByClassName(`saleReportHideBox${printIndex}`)[0]
const thead = main.querySelector('thead').cloneNode(true); // 深克隆DOM节点
const tbody = main.querySelector('tbody').cloneNode(true); // 深克隆DOM节点
const container = document.querySelector('#hiddenBox');
const tempTable = document.createElement('table');
tempTable.appendChild(thead);
tempTable.appendChild(tbody);
tempTable.setAttribute('id', 'table-to-xls-saleRankReport'); // 给table添加id值与按钮上的table字段对应
container.appendChild(tempTable); // 把创建的节点添加到页面容器中
setShowLoading(false)
downloadBtnRef.current.handleDownload();
setShowExportTable(false)
tempTable.remove() // 防止重复打印一个内容
}
return (
<div ref={(el) => {
// 打印报表
if (!reqDetailList || reqDetailList.length === 0) return;
setPrintOut(el);
}} >
{
showLoading ?
<div
style={{
width: '100%',
height: '100%',
background: 'rgba(0,0,0,0.1)',
position: 'fixed',
zIndex: 5,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '15px 20px 10px',
background: '#fff',
borderRadius: '8px',
width: '200px'
}}>
<Spin />
<span style={{ marginLeft: '5px' }}>...</span>
</div>
</div> : ''
}
<div className={`saleReportHideBox${printIndex}`} style={{ position: 'fixed', zIndex: -1, top: 0, left: 0 }}>
{
showExportTable && reqDetailList && reqDetailList.length > 0 ?
<ProTable
columns={columns}
dataSource={reqDetailList}
pagination={false}
expandable={{
defaultExpandAllRows: true
}}
/> : ''
}
</div>
<div id='hiddenBox' style={{ position: 'fixed', zIndex: -1, top: 0, left: 0 }} />
<div style={{ backgroundColor: '#fff', display: 'flex' }}>
<div style={{
width: '100%',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
expandable={{
expandRowByClick: true
}}
headerTitle={'登录日志列表'}
scroll={{ y: 'calc(100vh - 450px)' }}
search={{ span: 6 }}
pagination={{ pageSize: 100 }}
request={async (params) => {
const req: any = {
SearchParameter: {
OPERATELOG_TYPES: '1000,1002',
OPERATE_DATE_Start: moment(params?.searchDate).format('YYYY-MM-DD'),
OPERATE_DATE_End: moment(params?.searchDate).format('YYYY-MM-DD'),
},
SortStr: 'OPERATE_DATE desc',
KeyWord: {
Key: 'USER_NAME,USER_LOGINIP,USER_LOGINPLACE,BROWSER_VERSION,OPERATING_SYSTEM',
Value: params?.searchValue || ''
},
PageIndex: 1,
PageSize: 999999
}
setSearchParams(params)
const data = await handleGetOPERATELOGList(req)
console.log('login', data);
if (data && data.length > 0) {
setReqDetailList(data)
return { data, success: true }
}
return { data: [], success: true }
}}
toolbar={{
actions: [
<span style={{ visibility: 'hidden' }}>
<ReactHTMLTableToExcel
buttonText={'导出excel'}
ref={downloadBtnRef}
table="table-to-xls-saleRankReport"
filename={`登录日志列表`}
sheet="sheet1"
/>
</span>,
<Button
key="new"
type="primary"
onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true)
setTimeout(() => {
setShowExportTable(true)
setTimeout(() => {
exportTable(e)
}, 100)
}, 100)
} else {
message.error('暂无数据可导出!')
}
}}
>
excel
</Button>
]
}}
/>
</div>
</div>
</div>
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.currentUser
}))(saleRankReport);

View File

@ -0,0 +1,14 @@
import request from "@/utils/request"
// 获取操作日志表列表
export async function handleGetOPERATELOGList(params?: any){
const data = await request('/Log/GetOPERATELOGList', {
method: 'POST',
data: params,
})
if (data.Result_Code !== 100) {
return {}
}
return data.Result_Data.List
}

View File

@ -59,7 +59,7 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
const [checkedRole, setCheckedRole] = useState<React.Key[]>(); // 当前选中的行 角色 id
const [checkedServerpart, setCheckedServerpart] = useState<React.Key[]>(); // 当前选中的行 服务区id
const [checkedShop, setCheckedShop] = useState<React.Key[]>(); // 当前选中的行 服务区门店id
const [initialDetail, setInitialDetail] = useState<UserModel | any>(detail || {}); // 当前编辑页面的账户数据
const [initialDetail, setInitialDetail] = useState<any>(detail || {}); // 当前编辑页面的账户数据
const [copyData, setCopyData] = useState<any>()// 拷贝一份一开始接口返回的表单数据 与 最后提交可以进行对比
const [companyList, setCompanyList] = useState<any>()// 用户下面的公司列表
const [selectDetail, setSelectDetail] = useState<any>()// 选中的业主单位的省份
@ -185,7 +185,7 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
const getServiceList = async (PROVINCEID: any, id: any) => {
const typeId = id || showServiceList
const req = {
ProvinceCode: PROVINCEID || selectDetail,
ProvinceCode: PROVINCEID || selectDetail || '530000',
ServerpartType: typeId === 1020 ? 1010 : 1000,
ShowWholePower: true
}
@ -456,15 +456,16 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
if (newData.AnalysisPermission !== copyData.AnalysisPermission) {
handleAnalysisPermission({
userId: copyData.USER_ID,
AuthorType: (selectDetail === 340000 && tableTab === '1000' ||
selectDetail === 340000 && copyData?.AnalysisPermission === true && tableTab === '1000' ||
selectDetail === 340000 && copyData?.AnalysisPermission === false && tableTab === '1000') && newData.AnalysisPermission,
AuthorType: (selectDetail === 530000 && tableTab === '1000' ||
selectDetail === 530000 && copyData?.AnalysisPermission === true && tableTab === '1000' ||
selectDetail === 530000 && copyData?.AnalysisPermission === false && tableTab === '1000') && newData.AnalysisPermission,
OperateUser: currentUser?.Name
})
}
console.log('newValuenewValuenewValue', newValue);
const success = await handleAddUpdate(newValue, false);
console.log('tableTab', tableTab);
@ -650,7 +651,9 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
},
]}
request={async () => {
const options = await getOnwer()
const options = await getOnwer({ DataType: 1, ProvinceCode: "530000" })
console.log('optionsoptionsoptionsoptions', options);
return options;
}}
fieldProps={{
@ -665,8 +668,9 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
setSelectDetail(options.value)
getServiceList(options.value, showServiceList || null)
detailForm.current?.setFieldsValue({ USERTYPE_ID: '' })
setInitialDetail({ ...initialDetail, PROVINCE_UNIT: options.label, })
// if (initialDetail.USER_PATTERN === 4000){
// setInitialDetail({ ...initialDetail, PROVINCE_UNIT: options.label, })
setInitialDetail({ ...initialDetail, PROVINCE_UNIT: options.value, OWNERUNIT_NAME: options.label })
// if (initialDetail.USER_PATTERN === 4000){
// const req = {
// ProvinceCode:currentUser?.ProvinceCode,
// // OwnerUnitId:e
@ -741,18 +745,18 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
label="账号部门"
name="USERTYPE_ID"
dependencies={['USER_PROVINCE', 'USER_PATTERN', '']}
rules={[
{
required: initialDetail.USER_PATTERN === 1000 || initialDetail.USER_PATTERN === 4000,
message: '请选择账号部门',
},
]}
// rules={[
// {
// required: initialDetail.USER_PATTERN === 1000 || initialDetail.USER_PATTERN === 4000,
// message: '请选择账号部门',
// },
// ]}
request={async (params) => {
console.log('params', params);
if (pageType === 'merchantManagement') {
const req: any = {
// ProvinceCode: currentUser?.ProvinceCode || '340000',
// ProvinceCode: currentUser?.ProvinceCode || '530000',
UserTypePattern: 2000
}
const data = await getUserTypeTree(req)
@ -765,7 +769,7 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
return data
} if (params.USER_PROVINCE || params.USER_PATTERN) {
const req: any = {
ProvinceCode: params.USER_PATTERN === 2000 ? params.BUSINESSMAN_ID : params.USER_PROVINCE,
ProvinceCode: '530000',
UserTypePattern: tableTab === 9000 ? 1000 : tableTab
}
const data = await getUserTypeTree(req)
@ -926,9 +930,9 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
</Form.Item>
</Col>
{
selectDetail === 340000 && tableTab === '1000' ||
selectDetail === 340000 && copyData?.AnalysisPermission === true && tableTab === '1000' ||
selectDetail === 340000 && copyData?.AnalysisPermission === false && tableTab === '1000'
selectDetail === 530000 && tableTab === '1000' ||
selectDetail === 530000 && copyData?.AnalysisPermission === true && tableTab === '1000' ||
selectDetail === 530000 && copyData?.AnalysisPermission === false && tableTab === '1000'
?
<Col span={4}>
<Form.Item
@ -1051,7 +1055,7 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
</Card>
<Card title="授予账号权限" style={{ marginTop: 24 }} bordered={false} size="default">
<Row gutter={24}>
{
{/* {
tableTab === '1000' || tableTab === '4000' || tableTab === '9000' ?
<Col span={8}>
{serverpartTree &&
@ -1083,9 +1087,13 @@ const Edit = ({ tableTab, openType, detail, reloadTable, currentUser, selectTab,
></Tree>
</Form.Item>
</Card>}
</Col> : ''
}
</Col>
: ''
} */}
{/* {(currentUser?.UserPattern === 9000 ? initialDetail.USER_PATTERN === 1000 : currentUser?.UserPattern === 1000) && } */}
{
(initialDetail.USER_PATTERN === 2000 && initialDetail.BUSINESSMAN_NAME) || (pageType === 'merchantManagement' && BUSINESSMAN_ID?.label) ?
<Col span={8}>

View File

@ -25,6 +25,7 @@ import useRequest from "@ahooksjs/use-request";
import ProDescriptions from "@ant-design/pro-descriptions";
import { getUserSystemRole, getUserList, getUserTypeTree } from './service'
import { getOnwer } from "@/services/options";
const UserList: React.FC<{ currentUser: CurrentUser | undefined }> = (props) => {
const { currentUser } = props
@ -73,6 +74,7 @@ const UserList: React.FC<{ currentUser: CurrentUser | undefined }> = (props) =>
})
// 请求账号部门tree
const { run: getUserTypeMenu, loading: typeLoading, data: userTypeTree } = useRequest((type) => getUserTypeTree({ UserTypePattern: type || tableTab, ShowStatus: true }))
// const [userTypeMenu, setUserTypeMenu] = useState<{1000: {}, 2000: {}}>(()=>getUserTypeTree(tableTab)); // 分布更新窗口的弹窗
// 根据角色id查询权限树
const { run: getRoleMenu, data: roleMenuTree } = useRequest(async (SystemRoleId?: number) => {
@ -190,10 +192,17 @@ const UserList: React.FC<{ currentUser: CurrentUser | undefined }> = (props) =>
return (a.PROVINCE_UNIT || '').localeCompare((b.PROVINCE_UNIT || ''))
},
hideInTable: tableTab === '2000',
valueType: "treeSelect",
fieldProps: {
options: userTypeTree,
},
// valueType: "treeSelect",
valueType: "select",
request: async () => {
let data = await getOnwer({ ProvinceCode: "530000", DataType: 1 })
if (data && data.length > 0) {
data.forEach((item: any) => {
item.value = item.value.toString()
})
}
return data
}
},
{
title: '备注',
@ -289,6 +298,7 @@ const UserList: React.FC<{ currentUser: CurrentUser | undefined }> = (props) =>
...params,
USER_STATUS: params?.USER_STATUS,
USER_PATTERN: tableTab,
PROVINCE_UNIT: '911',
UserTypeIds: userTypeId && userTypeId[tableTab] ? userTypeId[tableTab].toString() : '',
SortStr: sortstr ? sortstr.toString() : 'USER_INDEX',
keyWord: params.searchKey ? { key: "USER_PASSPORT,USER_NAME,USER_MOBILEPHONE,PROVINCE_UNIT,BUSINESSMAN_NAME", value: params.searchKey } : null, // 关键词查询

View File

@ -5,7 +5,7 @@ import { useRef, useState } from "react";
import React from "react";
import { ExclamationCircleOutlined, setTwoToneColor } from "@ant-design/icons";
import Draggable from "react-draggable";
import ProForm, { ProFormCheckbox, ProFormDatePicker, ProFormDigit, ProFormText, ProFormTextArea, ProFormUploadButton } from "@ant-design/pro-form";
import ProForm, { ProFormCheckbox, ProFormDatePicker, ProFormDigit, ProFormSelect, ProFormText, ProFormTextArea, ProFormUploadButton } from "@ant-design/pro-form";
import session from "@/utils/session";
import moment from 'moment'
import { handleGetACTIVITYDETAILList, handleGetSynchroACTIVITY } from "../../SharedRestStationManagement/service";
@ -97,7 +97,7 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
{
title: <div style={{ textAlign: 'center' }}></div>,
width: 180,
dataIndex: "ACTIVITYDETAIL_STATE",
dataIndex: "ACTIVITYDETAIL_STATE",
ellipsis: true,
hideInSearch: true,
valueType: "select",
@ -193,6 +193,7 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
// } : {}}
request={async () => {
console.log('fileListfileListfileList', fileList);
console.log('parentRowparentRowparentRow', parentRow);
if (parentRow) {
let idList: any = []
@ -211,6 +212,8 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
...parentRow,
SERVERPART_ID: idList,
SERVERPART_SHOWNAME: nameStr,
ACTIVITY_TYPE: Number(parentRow.ACTIVITY_TYPE)
}
}
return {}
@ -253,7 +256,7 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
STAFF_ID: currentUser.ID,
STAFF_NAME: currentUser.Name,
OPERATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss'),
RelateList: RelateList
RelateList: RelateList,
}
} else {
req = {
@ -265,7 +268,7 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
STAFF_NAME: currentUser.Name,
OPERATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss'),
RelateList: RelateList,
ACTIVITY_STATE: 2
ACTIVITY_STATE: 2,
}
}
console.log('reqreqreqreq', req);
@ -344,6 +347,17 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
readonly={readonly}
/>
</Col>
<Col span={12}>
<ProFormSelect
label={"活动类型"}
name={"ACTIVITY_TYPE"}
readonly={readonly}
rules={[{ required: true, message: '请选择活动类型' }]}
fieldProps={{
options: [{ label: "活动报名", value: 1000 }, { label: "商城购物", value: 2000 }]
}}
/>
</Col>
<Col span={12}>
<ProFormText
label={"活动地点"}
@ -461,7 +475,7 @@ const EventRegistrationDetail = ({ parentRow, setparentRow, onShow, setOnShow, c
</ProForm>
{
parentRow ?
parentRow && Number(parentRow?.ACTIVITY_TYPE) === 1000 ?
<ProTable
headerTitle={'活动报名明细'}
actionRef={EventRegistrationRef}

View File

@ -57,3 +57,18 @@ export async function accountLogin(params: LoginParamsType): Promise<LoginResult
// export async function getFakeCaptcha(mobile: string) {
// return request(`/api/login/captcha?mobile=${mobile}`);
// }
// 登录日志的记录
export async function handleUpdateLoginLog(params?: any) {
const data = await request('/Log/SynchroOPERATELOG', {
method: 'POST',
data: params
})
if (data.Result_Code !== 100) {
return data
}
return data
}

View File

@ -271,10 +271,6 @@ export async function getOwnerUnitTree(DataType?: number | string): Promise<Comm
// 获取业主列表
export async function getOnwer(params?: any): Promise<CommonTypeModel[]> {
const ownerUnit = session.get(`ownerUnit`);
if (ownerUnit) {
return ownerUnit;
}
const data = await request('/BaseInfo/BindingOwnerUnitDDL', {
method: 'GET',
params

View File

@ -1,4 +1,4 @@
// 由 scripts/writeVersion.js 自动生成
export const VERSION = "4.5.74";
export const GIT_HASH = "8244fac";
export const BUILD_TIME = "2025-11-14T01:20:30.467Z";
export const VERSION = "4.5.77";
export const GIT_HASH = "1cd8c40";
export const BUILD_TIME = "2025-11-18T09:36:29.591Z";