diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1d3f0aa8..34310929 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -102,6 +102,7 @@
"codemirror",
"commitlint",
"cropperjs",
+ "echart",
"echarts",
"esnext",
"esno",
@@ -116,10 +117,12 @@
"sider",
"sortablejs",
"stylelint",
+ "svgs",
"unocss",
"unplugin",
"unref",
"videojs",
+ "VITE",
"vitejs",
"vueuse",
"wangeditor",
diff --git a/package.json b/package.json
index 21a731a8..fb0f23ad 100644
--- a/package.json
+++ b/package.json
@@ -36,20 +36,20 @@
"@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
- "axios": "^1.5.1",
+ "axios": "^1.6.0",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
- "diagram-js": "^12.5.0",
+ "diagram-js": "^12.6.0",
+ "driver.js": "^1.3.0",
"echarts": "^5.4.3",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.4.1",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
- "intro.js": "^7.2.0",
"jsencrypt": "^3.3.2",
"lodash-es": "^4.17.21",
"min-dash": "^4.1.1",
@@ -64,7 +64,7 @@
"video.js": "^7.21.5",
"vue": "^3.3.7",
"vue-dompurify-html": "^4.1.4",
- "vue-i18n": "^9.5.0",
+ "vue-i18n": "^9.6.2",
"vue-router": "^4.2.5",
"vue-types": "^5.1.1",
"vuedraggable": "^4.1.0",
@@ -72,20 +72,19 @@
"xml-js": "^1.6.11"
},
"devDependencies": {
- "@commitlint/cli": "^18.0.0",
- "@commitlint/config-conventional": "^18.0.0",
- "@iconify/json": "^2.2.132",
+ "@commitlint/cli": "^18.2.0",
+ "@commitlint/config-conventional": "^18.1.0",
+ "@iconify/json": "^2.2.135",
"@intlify/unplugin-vue-i18n": "^1.4.0",
"@purge-icons/generated": "^0.9.0",
- "@types/intro.js": "^5.1.3",
"@types/lodash-es": "^4.17.10",
- "@types/node": "^20.8.8",
+ "@types/node": "^20.8.9",
"@types/nprogress": "^0.2.2",
"@types/qrcode": "^1.5.4",
"@types/qs": "^6.9.9",
"@types/sortablejs": "^1.15.4",
- "@typescript-eslint/eslint-plugin": "^6.9.0",
- "@typescript-eslint/parser": "^6.9.0",
+ "@typescript-eslint/eslint-plugin": "^6.9.1",
+ "@typescript-eslint/parser": "^6.9.1",
"@unocss/transformer-variant-group": "^0.57.1",
"@unocss/eslint-config": "^0.57.1",
"@vitejs/plugin-legacy": "^4.1.1",
@@ -99,24 +98,24 @@
"eslint-config-prettier": "^9.0.0",
"eslint-define-config": "^1.24.1",
"eslint-plugin-prettier": "^5.0.1",
- "eslint-plugin-vue": "^9.18.0",
+ "eslint-plugin-vue": "^9.18.1",
"lint-staged": "^15.0.2",
"postcss": "^8.4.31",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.0.3",
"rimraf": "^5.0.5",
- "rollup": "^4.1.4",
- "sass": "^1.69.4",
+ "rollup": "^4.1.5",
+ "sass": "^1.69.5",
"stylelint": "^15.11.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3",
- "terser": "^5.22.0",
+ "terser": "^5.23.0",
"typescript": "5.2.2",
"unocss": "^0.57.1",
- "unplugin-auto-import": "^0.16.6",
+ "unplugin-auto-import": "^0.16.7",
"unplugin-element-plus": "^0.8.0",
"unplugin-vue-components": "^0.25.2",
"vite": "4.5.0",
@@ -128,7 +127,7 @@
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-top-level-await": "^1.3.1",
"vue-eslint-parser": "^9.3.2",
- "vue-tsc": "^1.8.20"
+ "vue-tsc": "^1.8.22"
},
"license": "MIT",
"repository": {
diff --git a/src/api/crm/product/index.ts b/src/api/crm/product/index.ts
new file mode 100644
index 00000000..cb1ddcda
--- /dev/null
+++ b/src/api/crm/product/index.ts
@@ -0,0 +1,43 @@
+import request from '@/config/axios'
+
+export interface ProductVO {
+ id: number
+ name: string
+ no: string
+ unit: string
+ price: number
+ status: number
+ categoryId: number
+ description: string
+ ownerUserId: number
+}
+
+// 查询产品列表
+export const getProductPage = async (params) => {
+ return await request.get({ url: `/crm/product/page`, params })
+}
+
+// 查询产品详情
+export const getProduct = async (id: number) => {
+ return await request.get({ url: `/crm/product/get?id=` + id })
+}
+
+// 新增产品
+export const createProduct = async (data: ProductVO) => {
+ return await request.post({ url: `/crm/product/create`, data })
+}
+
+// 修改产品
+export const updateProduct = async (data: ProductVO) => {
+ return await request.put({ url: `/crm/product/update`, data })
+}
+
+// 删除产品
+export const deleteProduct = async (id: number) => {
+ return await request.delete({ url: `/crm/product/delete?id=` + id })
+}
+
+// 导出产品 Excel
+export const exportProduct = async (params) => {
+ return await request.download({ url: `/crm/product/export-excel`, params })
+}
diff --git a/src/api/crm/productCategory/index.ts b/src/api/crm/productCategory/index.ts
new file mode 100644
index 00000000..6341d1bc
--- /dev/null
+++ b/src/api/crm/productCategory/index.ts
@@ -0,0 +1,33 @@
+import request from '@/config/axios'
+
+// TODO @zange:挪到 product 下,建个 category 包,挪进去哈;
+export interface ProductCategoryVO {
+ id: number
+ name: string
+ parentId: number
+}
+
+// 查询产品分类详情
+export const getProductCategory = async (id: number) => {
+ return await request.get({ url: `/crm/product-category/get?id=` + id })
+}
+
+// 新增产品分类
+export const createProductCategory = async (data: ProductCategoryVO) => {
+ return await request.post({ url: `/crm/product-category/create`, data })
+}
+
+// 修改产品分类
+export const updateProductCategory = async (data: ProductCategoryVO) => {
+ return await request.put({ url: `/crm/product-category/update`, data })
+}
+
+// 删除产品分类
+export const deleteProductCategory = async (id: number) => {
+ return await request.delete({ url: `/crm/product-category/delete?id=` + id })
+}
+
+// 产品分类列表
+export const getProductCategoryList = async (params) => {
+ return await request.get({ url: `/crm/product-category/list`, params })
+}
diff --git a/src/api/crm/receivablePlan/index.ts b/src/api/crm/receivablePlan/index.ts
index 178b21b5..f80f0572 100644
--- a/src/api/crm/receivablePlan/index.ts
+++ b/src/api/crm/receivablePlan/index.ts
@@ -2,7 +2,7 @@ import request from '@/config/axios'
export interface ReceivablePlanVO {
id: number
- indexNo: number
+ period: number
receivableId: number
status: number
checkStatus: string
diff --git a/src/api/system/social/client/index.ts b/src/api/system/social/client/index.ts
new file mode 100644
index 00000000..bf13ab49
--- /dev/null
+++ b/src/api/system/social/client/index.ts
@@ -0,0 +1,37 @@
+import request from '@/config/axios'
+
+export interface SocialClientVO {
+ id: number
+ name: string
+ socialType: number
+ userType: number
+ clientId: string
+ clientSecret: string
+ agentId: string
+ status: number
+}
+
+// 查询社交客户端列表
+export const getSocialClientPage = async (params) => {
+ return await request.get({ url: `/system/social-client/page`, params })
+}
+
+// 查询社交客户端详情
+export const getSocialClient = async (id: number) => {
+ return await request.get({ url: `/system/social-client/get?id=` + id })
+}
+
+// 新增社交客户端
+export const createSocialClient = async (data: SocialClientVO) => {
+ return await request.post({ url: `/system/social-client/create`, data })
+}
+
+// 修改社交客户端
+export const updateSocialClient = async (data: SocialClientVO) => {
+ return await request.put({ url: `/system/social-client/update`, data })
+}
+
+// 删除社交客户端
+export const deleteSocialClient = async (id: number) => {
+ return await request.delete({ url: `/system/social-client/delete?id=` + id })
+}
diff --git a/src/api/system/social/user/index.ts b/src/api/system/social/user/index.ts
new file mode 100644
index 00000000..f11231b7
--- /dev/null
+++ b/src/api/system/social/user/index.ts
@@ -0,0 +1,24 @@
+import request from '@/config/axios'
+
+export interface SocialUserVO {
+ id: number
+ type: number
+ openid: string
+ token: string
+ rawTokenInfo: string
+ nickname: string
+ avatar: string
+ rawUserInfo: string
+ code: string
+ state: string
+}
+
+// 查询社交用户列表
+export const getSocialUserPage = async (params) => {
+ return await request.get({ url: `/system/social-user/page`, params })
+}
+
+// 查询社交用户详情
+export const getSocialUser = async (id: number) => {
+ return await request.get({ url: `/system/social-user/get?id=` + id })
+}
diff --git a/src/components/Card/src/CardTitle.vue b/src/components/Card/src/CardTitle.vue
index 5b122f49..76a83564 100644
--- a/src/components/Card/src/CardTitle.vue
+++ b/src/components/Card/src/CardTitle.vue
@@ -3,7 +3,7 @@ defineComponent({
name: 'CardTitle'
})
-const { title } = defineProps({
+defineProps({
title: {
type: String,
required: true
diff --git a/src/components/ConfigGlobal/src/ConfigGlobal.vue b/src/components/ConfigGlobal/src/ConfigGlobal.vue
index a0873967..5bd90cf1 100644
--- a/src/components/ConfigGlobal/src/ConfigGlobal.vue
+++ b/src/components/ConfigGlobal/src/ConfigGlobal.vue
@@ -1,20 +1,19 @@
-
- {{ titel }}
+ {{ title }}
diff --git a/src/hooks/web/useGuide.ts b/src/hooks/web/useGuide.ts
new file mode 100644
index 00000000..7fd2fb09
--- /dev/null
+++ b/src/hooks/web/useGuide.ts
@@ -0,0 +1,49 @@
+import { Config, driver } from 'driver.js'
+import 'driver.js/dist/driver.css'
+import { useDesign } from '@/hooks/web/useDesign'
+import { useI18n } from '@/hooks/web/useI18n'
+
+const { t } = useI18n()
+
+const { variables } = useDesign()
+
+export const useGuide = (options?: Config) => {
+ const driverObj = driver(
+ options || {
+ showProgress: true,
+ nextBtnText: t('common.nextLabel'),
+ prevBtnText: t('common.prevLabel'),
+ doneBtnText: t('common.doneLabel'),
+ steps: [
+ {
+ element: `#${variables.namespace}-menu`,
+ popover: {
+ title: t('common.menu'),
+ description: t('common.menuDes'),
+ side: 'right'
+ }
+ },
+ {
+ element: `#${variables.namespace}-tool-header`,
+ popover: {
+ title: t('common.tool'),
+ description: t('common.toolDes'),
+ side: 'left'
+ }
+ },
+ {
+ element: `#${variables.namespace}-tags-view`,
+ popover: {
+ title: t('common.tagsView'),
+ description: t('common.tagsViewDes'),
+ side: 'bottom'
+ }
+ }
+ ]
+ }
+ )
+
+ return {
+ ...driverObj
+ }
+}
diff --git a/src/hooks/web/useIntro.ts b/src/hooks/web/useIntro.ts
deleted file mode 100644
index 7fe00845..00000000
--- a/src/hooks/web/useIntro.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import introJs from 'intro.js'
-import { IntroJs, Step, Options } from 'intro.js'
-import 'intro.js/introjs.css'
-
-import { useDesign } from '@/hooks/web/useDesign'
-
-export const useIntro = (setps?: Step[], options?: Options) => {
- const { t } = useI18n()
-
- const { variables } = useDesign()
-
- const defaultSetps: Step[] = setps || [
- {
- element: `#${variables.namespace}-menu`,
- title: t('common.menu'),
- intro: t('common.menuDes'),
- position: 'right'
- },
- {
- element: `#${variables.namespace}-tool-header`,
- title: t('common.tool'),
- intro: t('common.toolDes'),
- position: 'left'
- },
- {
- element: `#${variables.namespace}-tags-view`,
- title: t('common.tagsView'),
- intro: t('common.tagsViewDes'),
- position: 'bottom'
- }
- ]
-
- const defaultOptions: Options = options || {
- prevLabel: t('common.prevLabel'),
- nextLabel: t('common.nextLabel'),
- skipLabel: t('common.skipLabel'),
- doneLabel: t('common.doneLabel')
- }
-
- const introRef: IntroJs = introJs()
-
- introRef.addSteps(defaultSetps).setOptions(defaultOptions)
-
- return {
- introRef
- }
-}
diff --git a/src/hooks/web/useNetwork.ts b/src/hooks/web/useNetwork.ts
new file mode 100644
index 00000000..66fa4464
--- /dev/null
+++ b/src/hooks/web/useNetwork.ts
@@ -0,0 +1,21 @@
+import { ref, onBeforeUnmount } from 'vue'
+
+const useNetwork = () => {
+ const online = ref(true)
+
+ const updateNetwork = () => {
+ online.value = navigator.onLine
+ }
+
+ window.addEventListener('online', updateNetwork)
+ window.addEventListener('offline', updateNetwork)
+
+ onBeforeUnmount(() => {
+ window.removeEventListener('online', updateNetwork)
+ window.removeEventListener('offline', updateNetwork)
+ })
+
+ return { online }
+}
+
+export { useNetwork }
diff --git a/src/hooks/web/useNow.ts b/src/hooks/web/useNow.ts
new file mode 100644
index 00000000..09d3176b
--- /dev/null
+++ b/src/hooks/web/useNow.ts
@@ -0,0 +1,60 @@
+import { dateUtil } from '@/utils/dateUtil'
+import { reactive, toRefs } from 'vue'
+import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
+
+export const useNow = (immediate = true) => {
+ let timer: IntervalHandle
+
+ const state = reactive({
+ year: 0,
+ month: 0,
+ week: '',
+ day: 0,
+ hour: '',
+ minute: '',
+ second: 0,
+ meridiem: ''
+ })
+
+ const update = () => {
+ const now = dateUtil()
+
+ const h = now.format('HH')
+ const m = now.format('mm')
+ const s = now.get('s')
+
+ state.year = now.get('y')
+ state.month = now.get('M') + 1
+ state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
+ state.day = now.get('date')
+ state.hour = h
+ state.minute = m
+ state.second = s
+
+ state.meridiem = now.format('A')
+ }
+
+ function start() {
+ update()
+ clearInterval(timer)
+ timer = setInterval(() => update(), 1000)
+ }
+
+ function stop() {
+ clearInterval(timer)
+ }
+
+ tryOnMounted(() => {
+ immediate && start()
+ })
+
+ tryOnUnmounted(() => {
+ stop()
+ })
+
+ return {
+ ...toRefs(state),
+ start,
+ stop
+ }
+}
diff --git a/src/hooks/web/useTagsView.ts b/src/hooks/web/useTagsView.ts
new file mode 100644
index 00000000..31eadb02
--- /dev/null
+++ b/src/hooks/web/useTagsView.ts
@@ -0,0 +1,63 @@
+import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
+import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
+import { computed, nextTick, unref } from 'vue'
+
+export const useTagsView = () => {
+ const tagsViewStore = useTagsViewStoreWithOut()
+
+ const { replace, currentRoute } = useRouter()
+
+ const selectedTag = computed(() => tagsViewStore.getSelectedTag)
+
+ const closeAll = (callback?: Fn) => {
+ tagsViewStore.delAllViews()
+ callback?.()
+ }
+
+ const closeLeft = (callback?: Fn) => {
+ tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
+ callback?.()
+ }
+
+ const closeRight = (callback?: Fn) => {
+ tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
+ callback?.()
+ }
+
+ const closeOther = (callback?: Fn) => {
+ tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
+ callback?.()
+ }
+
+ const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
+ if (view?.meta?.affix) return
+ tagsViewStore.delView(view || unref(currentRoute))
+
+ callback?.()
+ }
+
+ const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
+ tagsViewStore.delCachedView()
+ const { path, query } = view || unref(currentRoute)
+ await nextTick()
+ replace({
+ path: '/redirect' + path,
+ query: query
+ })
+ callback?.()
+ }
+
+ const setTitle = (title: string, path?: string) => {
+ tagsViewStore.setTitle(title, path)
+ }
+
+ return {
+ closeAll,
+ closeLeft,
+ closeRight,
+ closeOther,
+ closeCurrent,
+ refreshPage,
+ setTitle
+ }
+}
diff --git a/src/hooks/web/useValidator.ts b/src/hooks/web/useValidator.ts
index 0c16fa31..151e35b2 100644
--- a/src/hooks/web/useValidator.ts
+++ b/src/hooks/web/useValidator.ts
@@ -1,54 +1,53 @@
-const { t } = useI18n()
+import { useI18n } from '@/hooks/web/useI18n'
+import { FormItemRule } from 'element-plus'
-type Callback = (error?: string | Error | undefined) => void
+const { t } = useI18n()
interface LengthRange {
min: number
max: number
- message: string
+ message?: string
}
export const useValidator = () => {
- const required = (message?: string) => {
+ const required = (message?: string): FormItemRule => {
return {
required: true,
message: message || t('common.required')
}
}
- const lengthRange = (val: any, callback: Callback, options: LengthRange) => {
+ const lengthRange = (options: LengthRange): FormItemRule => {
const { min, max, message } = options
- if (val.length < min || val.length > max) {
- callback(new Error(message))
- } else {
- callback()
+
+ return {
+ min,
+ max,
+ message: message || t('common.lengthRange', { min, max })
}
}
- const notSpace = (val: any, callback: Callback, message: string) => {
- // 用户名不能有空格
- if (val.indexOf(' ') !== -1) {
- callback(new Error(message))
- } else {
- callback()
+ const notSpace = (message?: string): FormItemRule => {
+ return {
+ validator: (_, val, callback) => {
+ if (val?.indexOf(' ') !== -1) {
+ callback(new Error(message || t('common.notSpace')))
+ } else {
+ callback()
+ }
+ }
}
}
- const notSpecialCharacters = (val: any, callback: Callback, message: string) => {
- // 密码不能是特殊字符
- if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
- callback(new Error(message))
- } else {
- callback()
- }
- }
-
- // 两个字符串是否想等
- const isEqual = (val1: string, val2: string, callback: Callback, message: string) => {
- if (val1 === val2) {
- callback()
- } else {
- callback(new Error(message))
+ const notSpecialCharacters = (message?: string): FormItemRule => {
+ return {
+ validator: (_, val, callback) => {
+ if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
+ callback(new Error(message || t('common.notSpecialCharacters')))
+ } else {
+ callback()
+ }
+ }
}
}
@@ -56,7 +55,6 @@ export const useValidator = () => {
required,
lengthRange,
notSpace,
- notSpecialCharacters,
- isEqual
+ notSpecialCharacters
}
}
diff --git a/src/layout/components/Message/src/Message.vue b/src/layout/components/Message/src/Message.vue
index 28f796b3..3019df29 100644
--- a/src/layout/components/Message/src/Message.vue
+++ b/src/layout/components/Message/src/Message.vue
@@ -53,7 +53,7 @@ onMounted(() => {
-
+
@@ -67,7 +67,7 @@ onMounted(() => {
-
+
@@ -88,6 +88,7 @@ onMounted(() => {
}
.message-list {
+ height: 400px;
display: flex;
flex-direction: column;
diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index 5a818fa9..9fe429e9 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -116,6 +116,7 @@ export enum DICT_TYPE {
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
+ SYSTEM_SOCIAL_TYPE = 'system_social_type',
// ========== INFRA 模块 ==========
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
@@ -193,5 +194,6 @@ export enum DICT_TYPE {
CRM_RETURN_TYPE = 'crm_return_type',
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
- CRM_CUSTOMER_SOURCE = 'crm_customer_source'
+ CRM_CUSTOMER_SOURCE = 'crm_customer_source',
+ CRM_PRODUCT_STATUS = 'crm_product_status'
}
diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts
index 53ccda11..e2ffcadd 100644
--- a/src/utils/formatTime.ts
+++ b/src/utils/formatTime.ts
@@ -334,6 +334,6 @@ export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
export function getDateRange(
beginDate: dayjs.ConfigType,
endDate: dayjs.ConfigType
-): [dayjs.ConfigType, dayjs.ConfigType] {
- return [dayjs(beginDate).startOf('d'), dayjs(endDate).endOf('d')]
+): [string, string] {
+ return [dayjs(beginDate).startOf('d').toString(), dayjs(endDate).endOf('d').toString()]
}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index d5301ddb..10f57567 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -34,6 +34,13 @@ export const underlineToHump = (str: string): string => {
})
}
+/**
+ * 驼峰转横杠
+ */
+export const humpToDash = (str: string): string => {
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase()
+}
+
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
dom.style.setProperty(prop, val)
}
@@ -67,7 +74,7 @@ export const trim = (str: string) => {
* @param {Date | number | string} time 需要转换的时间
* @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss
*/
-export const formatTime = (time: Date | number | string, fmt: string) => {
+export function formatTime(time: Date | number | string, fmt: string) {
if (!time) return ''
else {
const date = new Date(time)
@@ -98,7 +105,7 @@ export const formatTime = (time: Date | number | string, fmt: string) => {
/**
* 生成随机字符串
*/
-export const toAnyString = () => {
+export function toAnyString() {
const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
const r: number = (Math.random() * 16) | 0
const v: number = c === 'x' ? r : (r & 0x3) | 0x8
@@ -107,6 +114,13 @@ export const toAnyString = () => {
return str
}
+/**
+ * 首字母大写
+ */
+export function firstUpperCase(str: string) {
+ return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
+}
+
export const generateUUID = () => {
if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === 'function') {
diff --git a/src/views/Profile/Index.vue b/src/views/Profile/Index.vue
index b05f93cc..8e1695b5 100644
--- a/src/views/Profile/Index.vue
+++ b/src/views/Profile/Index.vue
@@ -15,7 +15,7 @@
-
+
@@ -23,17 +23,18 @@
-
+
-