@xmszm/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/index.cjs +2 -0
  2. package/dist/index.mjs +2145 -0
  3. package/dist/style.css +1 -0
  4. package/docs/components/dataform.md +61 -0
  5. package/docs/components/datatable.md +77 -0
  6. package/docs/components/dialog.md +78 -0
  7. package/docs/components/options.md +55 -0
  8. package/docs/components/query.md +49 -0
  9. package/docs/components/utils.md +56 -0
  10. package/docs/guide/demo.md +213 -0
  11. package/docs/guide/quickstart.md +77 -0
  12. package/docs/index.md +25 -0
  13. package/docs/usage.md +61 -0
  14. package/examples/demo.vue +224 -0
  15. package/package.json +64 -0
  16. package/src/dialog/commonDialog.jsx +230 -0
  17. package/src/dialog/style/commonDialog.less +40 -0
  18. package/src/dialog/utils/dialog.js +82 -0
  19. package/src/enum/options.js +3 -0
  20. package/src/enum/sort.jsx +31 -0
  21. package/src/form/DataForm.vue +125 -0
  22. package/src/image/ImagesUpload.vue +268 -0
  23. package/src/image/SvgIcon.vue +30 -0
  24. package/src/index.js +46 -0
  25. package/src/list/useList.jsx +99 -0
  26. package/src/options/Options.jsx +338 -0
  27. package/src/options/defaultOptions.jsx +580 -0
  28. package/src/options/options.md +77 -0
  29. package/src/plugin/vite/initRouteMeta.js +54 -0
  30. package/src/query/CommonQuery.vue +272 -0
  31. package/src/store/utils/index.js +6 -0
  32. package/src/table/DataTable.vue +315 -0
  33. package/src/table/FilterDialog.vue +157 -0
  34. package/src/table/opr/DataColumnCollet.jsx +127 -0
  35. package/src/table/opr/useDataColumn.jsx +196 -0
  36. package/src/table/opr/useDataColumnButton.jsx +56 -0
  37. package/src/table/opr/useDataColumnPop.jsx +57 -0
  38. package/src/table/test.md +248 -0
  39. package/src/table/utils/ellipsis.js +22 -0
  40. package/src/utils/array.js +26 -0
  41. package/src/utils/auth.js +118 -0
  42. package/src/utils/object.js +32 -0
  43. package/src/utils/time.js +7 -0
  44. package/src/utils/upload.js +46 -0
  45. package/types/index.d.ts +67 -0
package/docs/index.md ADDED
@@ -0,0 +1,25 @@
1
+ ---
2
+ layout: home
3
+ title: Nex Core
4
+ hero:
5
+ name: Nex Core
6
+ text: Vue 3 + Naive UI 的表单、表格、弹窗与工具集合
7
+ tagline: 开箱即用的配置式表单与表格方案,内置弹窗、上传、权限、路由工具。
8
+ actions:
9
+ - theme: brand
10
+ text: 快速开始
11
+ link: /guide/quickstart
12
+ - theme: alt
13
+ text: 查看组件
14
+ link: /components/dataform
15
+ features:
16
+ - title: 配置化表单
17
+ details: 通过 Options 定义字段,自动生成 rules,轻松构建复杂表单。
18
+ - title: 表格增强
19
+ details: 内置操作列创建器、排序、筛选、虚拟滚动与省略 tooltip。
20
+ - title: 弹窗集成
21
+ details: commonDialogMethod 将表单与弹窗能力合并,减少样板代码。
22
+ - title: 实用工具
23
+ details: 上传、权限、路由 meta 初始化、数组对象转换等常用方法。
24
+ ---
25
+
package/docs/usage.md ADDED
@@ -0,0 +1,61 @@
1
+ # 使用指南(核心要点)
2
+
3
+ ## 安装
4
+ ```bash
5
+ npm install core
6
+ ```
7
+
8
+ 需要同时安装的 peer 依赖(版本可按项目统一):`vue`、`naive-ui`、`vue-router`、`dayjs`、`lodash-es`、`@vicons/ionicons5`。
9
+ 如项目使用 `@` 别名,请确保存在对应的打包/运行时配置。
10
+
11
+ ## 快速开始
12
+ ```vue
13
+ <script setup>
14
+ import { ref } from 'vue'
15
+ import { DataForm, DataTable, commonDialogMethod } from 'core'
16
+ import 'core/dist/style.css' // 如需默认样式
17
+
18
+ const formValue = ref({})
19
+ const formOptions = [
20
+ { key: 'name', label: '名称', way: 'input', required: true },
21
+ { key: 'type', label: '类型', way: 'select', options: [{ label: 'A', value: 'a' }] },
22
+ ]
23
+
24
+ const tableColumns = [
25
+ { title: '名称', key: 'name', width: 160 },
26
+ { title: '类型', key: 'type', width: 100 },
27
+ ]
28
+ const tableData = ref([{ name: '示例', type: 'a' }])
29
+
30
+ function openDialog() {
31
+ commonDialogMethod({
32
+ title: '示例弹窗',
33
+ options: formOptions,
34
+ valueData: { name: '张三' },
35
+ interfaceFn: async () => {
36
+ // 在此提交数据
37
+ },
38
+ })
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <div class="p-16">
44
+ <DataForm v-model:value="formValue" :options="formOptions" />
45
+ <DataTable :data="tableData" :columns="tableColumns" style="margin-top: 12px" />
46
+ <button @click="openDialog">打开弹窗</button>
47
+ </div>
48
+ </template>
49
+ ```
50
+
51
+ ## 主要导出清单
52
+ - 组件:`DataForm`、`Options`、`CommonQuery`、`DataTable`、`OprButton`、`Pop`
53
+ - 方法:`commonDialogMethod`、`createActionColumnJsx`、`initRules`
54
+ - 工具:`toArray`、`ArrayToObject`、`ObjectToArray`、`customUpload`、`registryUpload`、`getFileUrl`
55
+ - 常量:`orderEnum`、`globalLabelField`、`globalValueField`
56
+ - 路由辅助:`initRouteMeta`、`useApiConfig`、`useAuthPermission`、`cellectChildenPermission`
57
+
58
+ ## 常见提示
59
+ - 若打包时报 `@/utils/...` 未找到,请在宿主项目配置 `@` 路径或提供对应实现。
60
+ - Vue TS 项目可直接使用内置 `types/index.d.ts` 获取基础类型提示。
61
+
@@ -0,0 +1,224 @@
1
+ <script setup lang="jsx">
2
+ import { computed, onMounted, reactive } from 'vue'
3
+ import { NButton, NCard, NSpace } from 'naive-ui'
4
+ import {
5
+ commonDialogMethod,
6
+ CommonQuery,
7
+ createActionColumnJsx,
8
+ DataTable,
9
+ } from 'core'
10
+ import 'core/dist/style.css'
11
+
12
+ // 查询与分页状态(可替换为业务方的 useNaivePage)
13
+ const listQuery = reactive({
14
+ page: 1,
15
+ pageSize: 10,
16
+ desc: true,
17
+ likeQuery: {
18
+ name1: '',
19
+ name2: '',
20
+ name3: '',
21
+ },
22
+ })
23
+
24
+ const pageState = reactive({
25
+ data: [],
26
+ itemCount: 0,
27
+ loading: false,
28
+ page: computed(() => listQuery.page),
29
+ pageSize: computed(() => listQuery.pageSize),
30
+ showSizePicker: true,
31
+ pageSizes: [10, 20, 50],
32
+ onUpdatePage: (p) => {
33
+ listQuery.page = p
34
+ pageState.fetchData()
35
+ },
36
+ onUpdatePageSize: (ps) => {
37
+ listQuery.pageSize = ps
38
+ listQuery.page = 1
39
+ pageState.fetchData()
40
+ },
41
+ fetchData: async () => {
42
+ pageState.loading = true
43
+ try {
44
+ // 模拟请求
45
+ const mock = Array.from({ length: 25 }).map((_, i) => ({
46
+ id: i + 1,
47
+ name: `名称-${i + 1}`,
48
+ type: i % 2 ? 'B' : 'A',
49
+ }))
50
+ pageState.itemCount = mock.length
51
+ const start = (listQuery.page - 1) * listQuery.pageSize
52
+ const end = start + listQuery.pageSize
53
+ pageState.data = mock.slice(start, end)
54
+ }
55
+ finally {
56
+ pageState.loading = false
57
+ }
58
+ },
59
+ search: () => {
60
+ listQuery.page = 1
61
+ pageState.fetchData()
62
+ },
63
+ reset: () => {
64
+ listQuery.likeQuery = { name1: '', name2: '', name3: '' }
65
+ listQuery.page = 1
66
+ pageState.fetchData()
67
+ },
68
+ })
69
+
70
+ // 查询项
71
+ const keyQuery = [
72
+ { label: '名称1', key: 'name1', queryType: 'likeQuery' },
73
+ { label: '名称2', key: 'name2', queryType: 'likeQuery' },
74
+ { label: '名称3', key: 'name3', queryType: 'likeQuery' },
75
+ ]
76
+
77
+ // 表格列与操作列
78
+ const columns = [
79
+ { title: '名称', key: 'name', width: 160 },
80
+ { title: '类型', key: 'type', width: 120 },
81
+ ]
82
+ const defaultColumns = []
83
+ const selectColumns = { type: 'selection', width: '40px' }
84
+ const opr = createActionColumnJsx([
85
+ {
86
+ label: '编辑',
87
+ type: 'primary',
88
+ onClick: row => onAdd(row, 'edit'),
89
+ },
90
+ {
91
+ label: '删除',
92
+ type: 'error',
93
+ mode: 'pop',
94
+ onClick: (row) => {
95
+ // del(row.id).then(() => pageState.fetchData())
96
+ console.log('删除', row)
97
+ },
98
+ },
99
+ ])
100
+
101
+ // 弹窗新增/编辑
102
+ function onAdd(row = null, mode = 'add') {
103
+ commonDialogMethod({
104
+ title: '示例弹窗',
105
+ mode,
106
+ options: [
107
+ { key: 'name', label: '名称', way: 'input', required: true },
108
+ {
109
+ key: 'type',
110
+ label: '类型',
111
+ way: 'select',
112
+ options: [
113
+ { label: '类型A', value: 'A' },
114
+ { label: '类型B', value: 'B' },
115
+ ],
116
+ },
117
+ ],
118
+ valueData: { ...row },
119
+ interfaceFn: async (data, { close }) => {
120
+ console.log('提交数据', data)
121
+ // await save(data)
122
+ pageState.fetchData()
123
+ close()
124
+ },
125
+ })
126
+ }
127
+
128
+ // 导出示例
129
+ const exportLoading = reactive({ value: false })
130
+ function onExport() {
131
+ exportLoading.value = true
132
+ // exportApi(listQuery).finally(() => (exportLoading.value = false))
133
+ setTimeout(() => (exportLoading.value = false), 800)
134
+ }
135
+
136
+ onMounted(() => pageState.fetchData())
137
+ </script>
138
+
139
+ <template>
140
+ <div class="page-box">
141
+ <!-- 头部筛选 -->
142
+ <NCard class="page-head">
143
+ <CommonQuery
144
+ :query="listQuery"
145
+ :options="keyQuery"
146
+ @submit="pageState.search()"
147
+ @reset="pageState.reset()"
148
+ />
149
+ </NCard>
150
+
151
+ <!-- 操作区 -->
152
+ <NSpace justify="space-between">
153
+ <NSpace>
154
+ <NButton type="primary" @click="onAdd()">新增</NButton>
155
+ <NButton type="primary" :loading="exportLoading.value" @click="onExport">
156
+ 导出
157
+ </NButton>
158
+ </NSpace>
159
+ </NSpace>
160
+
161
+ <!-- 内容表格区 -->
162
+ <div class="page-main">
163
+ <DataTable
164
+ :data="pageState.data"
165
+ :pagination="pageState"
166
+ :columns="columns"
167
+ :opr-columns="opr"
168
+ :default-columns="defaultColumns"
169
+ :row-key="row => row?.id"
170
+ :select-columns="selectColumns"
171
+ :loading="pageState.loading"
172
+ />
173
+ </div>
174
+ </div>
175
+ </template>
176
+
177
+ <style scoped lang="less">
178
+ .page-head {
179
+ border-radius: 8px;
180
+ background-color: #fff;
181
+ box-sizing: border-box;
182
+ display: flex;
183
+ flex-direction: column;
184
+ row-gap: 20px;
185
+ }
186
+
187
+ .page-main {
188
+ flex: 0 1 auto;
189
+ background-color: #fff;
190
+ border-radius: 8px;
191
+ padding: 20px;
192
+ box-sizing: border-box;
193
+ display: flex;
194
+ height: 100%;
195
+ flex-direction: column;
196
+ row-gap: 10px;
197
+
198
+ .n-data-table {
199
+ .n-data-table-th {
200
+ background-color: #f7f8fa;
201
+ border-right: 1px solid var(--n-merged-border-color);
202
+ white-space: break-spaces;
203
+ }
204
+ .n-data-table-th--last {
205
+ border-right: 1px solid transparent;
206
+ }
207
+ .n-data-table-td--last-row {
208
+ border-bottom: 1px solid var(--n-merged-border-color);
209
+ }
210
+ }
211
+ }
212
+
213
+ .page-box {
214
+ display: flex;
215
+ flex-direction: column;
216
+ height: calc(100vh - 120px); // 可按实际头/标签高度调整
217
+ margin: -16px;
218
+ padding: 24px;
219
+ box-sizing: border-box;
220
+ overflow: hidden;
221
+ row-gap: 20px;
222
+ }
223
+ </style>
224
+
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@xmszm/core",
3
+ "version": "0.0.1",
4
+ "description": "naiveui core 组件与工具库",
5
+ "license": "UNLICENSED",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./types/index.d.ts",
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "registry": "https://registry.npmjs.org/"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+ssh://git@github.com:xmszm/core.git"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.mjs",
20
+ "require": "./dist/index.cjs",
21
+ "types": "./types/index.d.ts"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src",
28
+ "types",
29
+ "docs",
30
+ "examples"
31
+ ],
32
+ "scripts": {
33
+ "build": "vite build",
34
+ "clean": "rimraf dist",
35
+ "docs:dev": "vitepress dev docs",
36
+ "docs:build": "vitepress build docs",
37
+ "docs:preview": "vitepress preview docs"
38
+ },
39
+ "peerDependencies": {
40
+ "@vicons/ionicons5": "^0.13.0",
41
+ "dayjs": "^1.11.0",
42
+ "lodash-es": "^4.17.21",
43
+ "naive-ui": "^2.38.0",
44
+ "vue": "^3.3.0",
45
+ "vue-router": "^4.2.0"
46
+ },
47
+ "devDependencies": {
48
+ "@vicons/ionicons5": "^0.13.0",
49
+ "@vitejs/plugin-vue": "^5.1.2",
50
+ "@vitejs/plugin-vue-jsx": "^4.0.1",
51
+ "dayjs": "^1.11.0",
52
+ "less": "^4.2.0",
53
+ "lodash-es": "^4.17.21",
54
+ "naive-ui": "^2.38.0",
55
+ "rimraf": "^6.0.1",
56
+ "vite": "^5.4.0",
57
+ "vitepress": "^1.2.3",
58
+ "vue": "^3.3.0",
59
+ "vue-router": "^4.2.0"
60
+ },
61
+ "dependencies": {
62
+ "npm": "^11.7.0"
63
+ }
64
+ }
@@ -0,0 +1,230 @@
1
+ import { NButton, NSpace } from 'naive-ui'
2
+ import { computed, reactive, ref, unref, watch } from 'vue'
3
+ import DataForm from '../form/DataForm.vue'
4
+ import { dialogDefaultOption } from './utils/dialog'
5
+ import './style/commonDialog.less'
6
+
7
+ /**
8
+ *
9
+ * @param {*} param
10
+ * @param {object} dialogProps
11
+ * @returns {object {cancel,model}}
12
+ * 集成填写表单功能的弹窗
13
+ */
14
+ export function commonDialogMethod(
15
+ {
16
+ title = '',
17
+ noTitle = false,
18
+ titleFull = null,
19
+ options = [],
20
+ mode = 'add',
21
+ modeEnum = {},
22
+ labelField = 'label',
23
+ isNo = true,
24
+ formProps = {},
25
+ interfaceFn = null,
26
+ interfaceFnCancel = null,
27
+ valueData,
28
+ read,
29
+ isRead,
30
+ action = null,
31
+ contentStyle = {},
32
+ actionProps = {},
33
+ } = {
34
+ title: '自定义弹窗',
35
+ noTitle: false,
36
+ action: [],
37
+ options: [],
38
+ read: false,
39
+ isRead: false,
40
+ valueData: {},
41
+ },
42
+ dialogProps = null,
43
+ ) {
44
+ const defaultModeEnum = {
45
+ none: { sub: '', read: false },
46
+ create: { sub: '创建', read: false },
47
+ add: { sub: '添加', read: false },
48
+ edit: { sub: '编辑', read: false },
49
+ view: { sub: '查看', read: true },
50
+ export: { sub: '导出', read: false },
51
+ import: { sub: '导入', read: false },
52
+ delete: { sub: '删除', read: false },
53
+ copy: { sub: '复制', read: false },
54
+ ...modeEnum,
55
+ }
56
+ const formRef = ref()
57
+ const actionLoading = reactive([])
58
+ const model = ref({ ...valueData })
59
+ const defaultActionProps = {
60
+ justify: 'end',
61
+ wrapItem: false,
62
+ style: {
63
+ width: '100%',
64
+ },
65
+ }
66
+
67
+ const defaultButtonStyle = {
68
+ width: '120px',
69
+ }
70
+ const defaultAction = [
71
+ {
72
+ label: '取消',
73
+ props: {
74
+ type: 'primary',
75
+ ghost: true,
76
+ },
77
+ onClick: ({ cancel }) => {
78
+ if (interfaceFnCancel) {
79
+ interfaceFnCancel(unref(model), {
80
+ close: cancel,
81
+ })
82
+ }else {
83
+ cancel()
84
+ }
85
+ },
86
+ },
87
+ {
88
+ label: '确定',
89
+ valid: true,
90
+ onClick: async ({ cancel, hideLoading }) =>
91
+ interfaceFn
92
+ ? await interfaceFn(unref(model), {
93
+ close: cancel,
94
+ hideLoading,
95
+ })
96
+ : cancel(),
97
+ },
98
+ ]
99
+
100
+ action
101
+ || defaultAction.forEach((v, i) => {
102
+ actionLoading[i] = false
103
+ })
104
+ const titleRender
105
+ = typeof titleFull === 'function'
106
+ ? () => titleFull(defaultModeEnum[mode]?.sub)
107
+ : titleFull
108
+
109
+ const d = $dialog.create({
110
+ type: 'info',
111
+ ...dialogDefaultOption,
112
+ ...(!noTitle
113
+ ? { title: titleRender || (defaultModeEnum[mode]?.sub ?? '') + title }
114
+ : {}),
115
+ style: {
116
+ width: '500px',
117
+ },
118
+ content: () => (
119
+ <DataForm
120
+ ref={v => {
121
+ formRef.value = v
122
+ }}
123
+ options={options}
124
+ v-model:value={model.value}
125
+ isNo={isNo}
126
+ read={read ?? isRead}
127
+ labelField={labelField}
128
+ formProps={formProps}
129
+ contentStyle={contentStyle}
130
+ dialog
131
+ />
132
+ ),
133
+ action: !(read ?? isRead)
134
+ ? typeof action === 'function'
135
+ ? () => action({ formRef, data: unref(model), d, close: cancel })
136
+ : () => (
137
+ <NSpace
138
+ {...defaultActionProps}
139
+ {...actionProps}
140
+ style={{
141
+ ...defaultActionProps?.style,
142
+ ...(actionProps?.style || {}),
143
+ }}
144
+ >
145
+ {(action || defaultAction).map((v, i) =>
146
+ v?.render
147
+ ? (
148
+ v?.render?.()
149
+ )
150
+ : (
151
+ <NButton
152
+ type="primary" ghost={v.mode === 'cancel'} {...({ ...v?.props, ...actionProps?.buttonProps } || {})}
153
+ style={{ ...defaultButtonStyle, ...(v?.style || {}) }}
154
+ loading={actionLoading[i]}
155
+ onClick={async () => {
156
+ if (v.mode === 'cancel') {
157
+ d.destroy()
158
+ }
159
+ else {
160
+ const showLoading = () => (actionLoading[i] = true)
161
+ const hideLoading = () => (actionLoading[i] = false)
162
+ if (v?.loading)
163
+ showLoading()
164
+ try {
165
+ console.log('model', unref(model));
166
+
167
+ if (v?.valid)
168
+ await validate()
169
+ console.log(v?.valid)
170
+ if (v?.valid || v?.loading)
171
+ showLoading()
172
+ await v?.onClick({
173
+ model: unref(model),
174
+ comfirm,
175
+ cancel,
176
+ validate,
177
+ showLoading,
178
+ hideLoading,
179
+ })
180
+ }
181
+ catch (e) {
182
+ console.log(e)
183
+ }
184
+ finally {
185
+ if (v?.valid || v?.loading)
186
+ hideLoading()
187
+ }
188
+ }
189
+ }}
190
+ >
191
+ {v.label || ''}
192
+ </NButton>
193
+ ),
194
+ )}
195
+ </NSpace>
196
+ )
197
+ : null,
198
+ // ...readButton.value,
199
+ ...dialogProps,
200
+ class: `core-dialog ${ unref(read) ? 'core-dialog-read' : ''} ${dialogProps?.class || ''}`,
201
+ })
202
+
203
+ function cancel() {
204
+ console.log('取消', model.value)
205
+
206
+ d?.destroy()
207
+ }
208
+
209
+ async function validate(arr = []) {
210
+ console.log('开启验证')
211
+ await formRef.value?.valid()
212
+ }
213
+
214
+ async function comfirm(fn) {
215
+ return fn && fn()
216
+ }
217
+
218
+ // onBeforeUnmount(() => {
219
+ // console.log('commomDialog end')
220
+ // cancel()
221
+ // })
222
+
223
+ return {
224
+ cancel,
225
+ setValue: (v, str) =>
226
+ str ? (model.value[str] = v) : (model.value = { ...model.value, ...v }),
227
+ model: unref(model),
228
+ modeEnum: defaultModeEnum,
229
+ }
230
+ }
@@ -0,0 +1,40 @@
1
+ .core-dialog-main,
2
+ .core-dialog-content {
3
+ box-sizing: border-box;
4
+
5
+ // max-height: 600px;
6
+ display: flex;
7
+ align-items: flex-start;
8
+ }
9
+
10
+ .core-dialog-main {
11
+ min-height: 300px;
12
+ }
13
+ .core-dialog-content {
14
+ // border-top: 1px solid #91919147;
15
+ }
16
+
17
+ .core-dialog {
18
+ .n-dialog__content {
19
+ overflow-y: auto;
20
+ margin: 10px -28px 0;
21
+ box-sizing: border-box;
22
+ flex: 1;
23
+ border-top: 1px solid #91919147;
24
+ border-bottom: 1px solid #91919147;
25
+ padding: 28px;
26
+ // max-height: 500px;
27
+ }
28
+
29
+ .n-dialog__action {
30
+ padding: 20px 0 0 0;
31
+ box-sizing: border-box;
32
+ }
33
+ }
34
+
35
+
36
+ .core-dialog-read{
37
+ .n-dialog__content {
38
+ border-bottom: unset;
39
+ }
40
+ }