vue3-django 0.1.6 → 0.1.7

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.
package/README.md CHANGED
@@ -209,6 +209,73 @@ ordering_fields 自动启用远程排序;字段 sortable 为 true 时仅排序
209
209
 
210
210
  ForeignKey 指向固定模型;GenericForeignKey 通过 content_type / object_id 映射目标,类型和 ID 字段名可配置。后者需要注册 contenttypes.contenttype 并提供 all/ 接口;未知类型或缺少路由时回退文本。控件自身的 error 事件目前不会由表格逐层转发。
211
211
 
212
+ ### Excel 导出
213
+
214
+ Table 默认提供 download 操作,导出传入的 rows;RemoteTable 和 ModelTable 按当前搜索、排序及固定查询条件获取全部记录,不只导出当前页。topActions 可覆盖或隐藏入口,保留 download 即可使用内置功能。
215
+
216
+ 导出文件为 .xlsx,文件名使用 title,模型表默认使用模型名称。按可见字段配置的顺序和 label 导出,保留原始数字、布尔和空值;支持 choices、formatter、嵌套 items、exportFormatter 和 export: false。不会执行单元格 widget 或导出 HTML 样式,文本不会自动转成公式。
217
+ excelGetAllData 可覆盖数据来源,excelFormat 可返回二维数组(首行为表头),excelWriter 可替换文件写入。ExcelJS 按需加载。
218
+
219
+ 远程每次请求 maxPageSize 条(默认 1000),分页顺序下载并合并,超出该数量会先提醒数据一致性风险;导出不改变当前列表、分页或选择。支持取消,自定义 request(params, { signal }) 应传递 signal。分页不是后端快照,即使数量没变化也可能重复或漏行;大量记录或强一致性导出建议使用后端导出接口配合 createDownloadService。
220
+
221
+ ## 应用状态管理
222
+
223
+ createDjangoApp 自动创建并安装应用级 store,通过返回值 application.store、组件中的 useDjangoStore() 或 $store 访问。不依赖 Vuex/Pinia,不共享全局单例,也不重复保存认证状态。
224
+
225
+ ```js
226
+ import { useDjangoStore } from 'vue3-django'
227
+
228
+ // 在组件 setup 中调用。
229
+ const store = useDjangoStore()
230
+ const user = store.user // computed ref,与 auth.state.user 同步
231
+ const menus = store.menus // computed ref,按模型权限生成菜单
232
+
233
+ store.can('update', 'crm.customer')
234
+ store.invalidate('crm.customer') // 通知已打开的模型列表刷新
235
+ ```
236
+
237
+ state 管理 apps、application(标题等配置)、party 和 revisions;user、ready、menus 为派生状态。登录、用户查询和退出分别调用 store.login / getUserInfo / logout,委托同一个 auth 实例。
238
+ 租户信息通过 store.getPartyInfo() 按需读取,默认接口 saas/party/current/;可通过 createDjangoApp 的 storeOptions.partyUrl 覆盖。切换用户或退出会清理租户与模型刷新状态。
239
+
240
+ store.subscribe((event, store) => ...) 订阅 user-ready、user-logout、party-ready、model-changed,返回取消订阅函数。应用卸载会清理订阅和监听;组件自行订阅时应在卸载时取消。
241
+ 自组装应用可使用 createDjangoStore({ auth, http, apps, application }) 并 app.use(store),同时将 store 和 store.state.revisions 传入 createDjango 的 context。默认入口也可传入 store,但须与应用使用同一个 auth 实例。
242
+ 这里不提供旧 Vuex commit/dispatch 或 Vue 2 事件总线接口;用户数据持久化、业务日志和额外业务状态由宿主按需实现。
243
+
244
+ ## 基础工具与服务
245
+
246
+ 工具按职责拆分:utils 为无框架状态的函数,core 服务显式注入依赖,browser 工具仅在调用时访问 DOM。不会修改 Array.prototype,也不会在导入时发起请求或读写 localStorage。
247
+
248
+ ```js
249
+ import { createStorage, createDownloadService, mapConcurrent, arrayDelta } from 'vue3-django'
250
+
251
+ const cache = createStorage({ namespace: 'dashboard', tenantId: 'school-a', userId: 12 })
252
+ cache.set('table-settings', { pageSize: 20 }, { ttl: 86400000 })
253
+ const settings = cache.get('table-settings', { pageSize: 10 })
254
+
255
+ const downloads = createDownloadService({ http: application.registry.http })
256
+ await downloads.download('crm/customer/export/', { params: { is_active: true } })
257
+
258
+ const controller = new AbortController()
259
+ const results = await mapConcurrent(
260
+ [1, 2, 3],
261
+ 2,
262
+ async (id, index, signal) => {
263
+ const response = await application.registry.http.get(`crm/customer/${id}/`, { signal })
264
+ return response.data
265
+ },
266
+ { signal: controller.signal },
267
+ )
268
+
269
+ const changes = arrayDelta([1, 2], [2, 3]) // { added: [3], removed: [1] }
270
+ ```
271
+
272
+ - createStorage 支持 JSON 值、毫秒 TTL 和可注入的 Storage 后端;保留 0/false/空字符串,损坏或过期时返回 fallback。set/remove/clear 返回是否成功,onError 接收错误。clear 只删除当前应用/租户/用户命名空间;配额失败不清空数据。不用于保存密码等敏感数据,切换用户时应创建对应用户的新实例。
273
+ - createDownloadService 使用注入的 HTTP 客户端,支持筛选、AbortSignal 和 Content-Disposition 文件名;默认拒绝绝对外部地址,避免意外发送应用认证信息。外部公开下载使用独立无认证客户端并显式设置 allowExternal。save 可替换,saveBlob 可单独使用。
274
+ - mapConcurrent 验证正整数并发数,按输入顺序返回结果,不修改输入。默认失败后停止调度,等待已启动任务结束后抛错;settled: true 收集每项结果。取消立即拒绝并停止后续调度,运行中的任务需自行使用传入的 signal。onProgress 接收 completed/total/index。
275
+ - arrayEquals 比较嵌套数组(对象按引用比较);arrayDelta 按值/引用返回去重后的 added/removed,不跟踪对象内部变化。
276
+
277
+ 批量导入、任务 SSE 监听、学习时长和业务日志不包含在这套基础工具中,按业务需求另行接入。
278
+
212
279
  ## 后端需要满足什么约定?
213
280
 
214
281
  默认模型接口相对于 apiBaseURL 为 app/model/,可用模型 url 覆盖。主键默认 id,可通过 idField 定制。
@@ -231,7 +298,7 @@ Session 的 CSRF cookie 由后端设置;跨域凭证、CORS 与 CSRF trusted o
231
298
 
232
299
  ## 当前边界
233
300
 
234
- - 特殊业务页面、导出和动作接口由宿主实现;框架不自动生成这些业务逻辑。
301
+ - 特殊业务页面、后端导出和动作接口由宿主实现;框架提供前端表格 Excel 导出,但不自动生成后端业务逻辑。
235
302
  - 复杂分组列、完整 ModelGrid 和历史 dialog 动作配置尚未实现。
236
303
  - 上传已实现前端初始化、单次 PUT/POST 直传与后端确认协议;业务需提供签名、确认和对象存储。分片、断点续传、视频转码/VOD 尚未实现。
237
304
  - 无效或未注册的关联不能自动补成可用业务模型。
@@ -1 +1 @@
1
- .vd-toolbar{display:flex;align-items:center;gap:8px;margin:0 0 8px}.vd-toolbar h2{flex:1;margin:0}.vd-toolbar .el-input{max-width:360px}.vd-card,.el-alert{margin-bottom:16px}body:has(.vd-app){margin:var(--vd-body-margin, 0)}.vd-app[v-cloak]{display:none}.vd-app{color:var(--vd-text-color, #253447);background:var(--vd-background, white);font-family:system-ui,-apple-system,sans-serif}.vd-app .vd-model-page{width:100%;box-sizing:border-box;padding:var(--vd-page-padding, 0 4px 12px)}.model-icon[data-v-5a104f44]{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.25em;height:1.25em;margin-right:8px;vertical-align:middle}.viewtabs[data-v-5703e450]{margin:0 8px}.viewtabs[data-v-5703e450] .el-tabs__header{margin-bottom:10px}.tab-icon[data-v-5703e450]{width:1em;height:1em;margin-right:6px}.vd-actions[data-v-2e52c126]{display:inline-flex;align-items:center;white-space:nowrap;vertical-align:middle}.vd-actions.is-compact[data-v-2e52c126] .el-button+.el-button{margin-left:0}.vd-actions.is-compact[data-v-2e52c126] .el-dropdown{margin-left:4px}.vd-actions-more[data-v-2e52c126]{border:0;background:transparent}.vd-actions[data-v-2e52c126] .vd-action-icon .model-icon{margin-right:0}.django-layout[data-v-3bbd5166]{--vd-header-height: var(--el-component-size);display:flex;flex-direction:column;min-height:100vh;background:#fff;color:#253447}.layout-header[data-v-3bbd5166]{min-height:var(--vd-header-height);padding:0 16px;display:flex;align-items:center;gap:12px;background:#fff;border-bottom:1px solid #e5eaf0}.layout-brand[data-v-3bbd5166]{font-weight:700;color:inherit;text-decoration:none}.layout-account[data-v-3bbd5166]{margin-left:auto;display:flex;align-items:center;gap:12px;min-width:0}.account-trigger[data-v-3bbd5166]{display:inline-flex;align-items:center;gap:10px;max-width:min(240px,40vw)}.account-trigger[data-v-3bbd5166]:focus-visible{outline:2px solid var(--el-color-primary, #409eff);outline-offset:-2px}.account-name[data-v-3bbd5166]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.account-arrow[data-v-3bbd5166]{flex-shrink:0;color:var(--el-text-color-placeholder, #a8abb2)}.layout-body[data-v-3bbd5166]{display:flex;flex:1}.layout-sidebar[data-v-3bbd5166]{width:190px;flex-shrink:0;background:#fff;border-right:1px solid #e5eaf0}.layout-sidebar[data-v-3bbd5166] .el-menu{border-right:0}.layout-content[data-v-3bbd5166]{min-width:0;flex:1;padding-top:0}.menu-toggle[data-v-3bbd5166],.menu-backdrop[data-v-3bbd5166]{display:none}.layout-content[data-v-3bbd5166] .viewtabs{margin:0 8px}@media(max-width:768px){.menu-toggle[data-v-3bbd5166]{display:inline-flex}.layout-header[data-v-3bbd5166]{padding:0 12px}.layout-sidebar[data-v-3bbd5166]{display:none}.layout-sidebar.opened[data-v-3bbd5166]{display:block;position:fixed;top:calc(var(--vd-header-height) + 1px);bottom:0;left:0;z-index:21;overflow:auto}.menu-backdrop[data-v-3bbd5166]{display:block;position:fixed;inset:calc(var(--vd-header-height) + 1px) 0 0;background:#0006;border:0;z-index:20}.layout-content[data-v-3bbd5166] .viewtabs{margin:0 8px}}.vd-upload[data-v-777ec908]{display:flex;flex-wrap:wrap;gap:8px}.vd-upload-item[data-v-777ec908]{display:flex;align-items:center;flex-wrap:wrap;gap:6px;max-width:100%}.vd-upload-item img[data-v-777ec908]{width:72px;height:72px;object-fit:contain}.vd-upload-item a[data-v-777ec908]{overflow-wrap:anywhere}.vd-upload button[data-v-777ec908]{cursor:pointer;border:1px solid #dcdfe6;border-radius:4px;background:#fff;padding:5px 10px;color:#409eff}.vd-cover-placeholder[data-v-52989853]{display:inline-flex;align-items:center;justify-content:center;min-height:32px;color:#909399;font-size:12px}.vd-login[data-v-a32e9ae3]{max-width:400px;margin:80px auto;padding:24px}h1[data-v-a32e9ae3]{font-size:24px}.vd-home[data-v-e81cec03]{padding:var(--vd-home-padding, 24px)}h1[data-v-e81cec03]{margin:0 0 12px;font-size:24px;font-weight:500}p[data-v-e81cec03]{margin:0;color:var(--el-text-color-secondary, #909399)}.vd-batch-actions[data-v-bf15a101]{display:flex;align-items:center;gap:6px;margin:4px 0}.vd-model-search[data-v-d6cb8ac9]{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px;margin-bottom:4px}.vd-search-field[data-v-d6cb8ac9]{flex:0 1 var(--search-field-width, 8rem);width:var(--search-field-width, 8rem);min-width:0;max-width:100%}.vd-search-field[data-v-d6cb8ac9] .el-input,.vd-search-field[data-v-d6cb8ac9] .el-select,.vd-search-field[data-v-d6cb8ac9] .el-input-number,.vd-search-field[data-v-d6cb8ac9] .el-date-editor{width:100%;min-width:0}.vd-search-range[data-v-d6cb8ac9]{display:flex;align-items:center;gap:4px}.foreignkey-link[data-v-72c5023a]{color:var(--el-color-primary, #409eff);text-decoration:none}.foreignkey-link[data-v-72c5023a]:hover{text-decoration:underline}.foreignkey-link[data-v-75351597]{color:var(--el-color-primary, #409eff);text-decoration:none}.foreignkey-link[data-v-75351597]:hover{text-decoration:underline}.vd-table-text[data-v-286ae7d1]{white-space:pre-wrap;overflow-wrap:anywhere}.vd-table-boolean.is-true[data-v-286ae7d1]{color:var(--el-color-success, green);font-weight:700}.vd-table-images[data-v-286ae7d1]{display:flex;flex-wrap:wrap;gap:4px}.vd-table-video[data-v-286ae7d1]{width:180px;max-width:100%}.vd-table-json[data-v-286ae7d1]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;margin:0}.vd-table-json dd[data-v-286ae7d1]{margin:0}@media(hover:hover)and (pointer:fine){[data-v-415a1a29] .vd-row-actions.hover-show{visibility:hidden}[data-v-415a1a29] .el-table__row:hover .vd-row-actions.hover-show{visibility:visible;cursor:pointer}}.vd-table-actions[data-v-415a1a29]{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-bottom:8px}.vd-table-pagination[data-v-560aef57]{padding:10px 0 6px}.vd-table-pagination[data-v-560aef57] .el-pagination{display:flex;flex-wrap:wrap;justify-content:flex-start;row-gap:8px}.vd-table-pagination.is-mobile[data-v-560aef57]{padding:12px 0}@media(max-width:600px){.vd-table-pagination[data-v-560aef57] .el-pagination__jump{margin-left:0}}
1
+ .vd-toolbar{display:flex;align-items:center;gap:8px;margin:0 0 8px}.vd-toolbar h2{flex:1;margin:0}.vd-toolbar .el-input{max-width:360px}.vd-card,.el-alert{margin-bottom:16px}body:has(.vd-app){margin:var(--vd-body-margin, 0)}.vd-app[v-cloak]{display:none}.vd-app{color:var(--vd-text-color, #253447);background:var(--vd-background, white);font-family:system-ui,-apple-system,sans-serif}.vd-app .vd-model-page{width:100%;box-sizing:border-box;padding:var(--vd-page-padding, 0 4px 12px)}.model-icon[data-v-5a104f44]{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.25em;height:1.25em;margin-right:8px;vertical-align:middle}.viewtabs[data-v-5703e450]{margin:0 8px}.viewtabs[data-v-5703e450] .el-tabs__header{margin-bottom:10px}.tab-icon[data-v-5703e450]{width:1em;height:1em;margin-right:6px}.vd-actions[data-v-2e52c126]{display:inline-flex;align-items:center;white-space:nowrap;vertical-align:middle}.vd-actions.is-compact[data-v-2e52c126] .el-button+.el-button{margin-left:0}.vd-actions.is-compact[data-v-2e52c126] .el-dropdown{margin-left:4px}.vd-actions-more[data-v-2e52c126]{border:0;background:transparent}.vd-actions[data-v-2e52c126] .vd-action-icon .model-icon{margin-right:0}.django-layout[data-v-3bbd5166]{--vd-header-height: var(--el-component-size);display:flex;flex-direction:column;min-height:100vh;background:#fff;color:#253447}.layout-header[data-v-3bbd5166]{min-height:var(--vd-header-height);padding:0 16px;display:flex;align-items:center;gap:12px;background:#fff;border-bottom:1px solid #e5eaf0}.layout-brand[data-v-3bbd5166]{font-weight:700;color:inherit;text-decoration:none}.layout-account[data-v-3bbd5166]{margin-left:auto;display:flex;align-items:center;gap:12px;min-width:0}.account-trigger[data-v-3bbd5166]{display:inline-flex;align-items:center;gap:10px;max-width:min(240px,40vw)}.account-trigger[data-v-3bbd5166]:focus-visible{outline:2px solid var(--el-color-primary, #409eff);outline-offset:-2px}.account-name[data-v-3bbd5166]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.account-arrow[data-v-3bbd5166]{flex-shrink:0;color:var(--el-text-color-placeholder, #a8abb2)}.layout-body[data-v-3bbd5166]{display:flex;flex:1}.layout-sidebar[data-v-3bbd5166]{width:190px;flex-shrink:0;background:#fff;border-right:1px solid #e5eaf0}.layout-sidebar[data-v-3bbd5166] .el-menu{border-right:0}.layout-content[data-v-3bbd5166]{min-width:0;flex:1;padding-top:0}.menu-toggle[data-v-3bbd5166],.menu-backdrop[data-v-3bbd5166]{display:none}.layout-content[data-v-3bbd5166] .viewtabs{margin:0 8px}@media(max-width:768px){.menu-toggle[data-v-3bbd5166]{display:inline-flex}.layout-header[data-v-3bbd5166]{padding:0 12px}.layout-sidebar[data-v-3bbd5166]{display:none}.layout-sidebar.opened[data-v-3bbd5166]{display:block;position:fixed;top:calc(var(--vd-header-height) + 1px);bottom:0;left:0;z-index:21;overflow:auto}.menu-backdrop[data-v-3bbd5166]{display:block;position:fixed;inset:calc(var(--vd-header-height) + 1px) 0 0;background:#0006;border:0;z-index:20}.layout-content[data-v-3bbd5166] .viewtabs{margin:0 8px}}.vd-upload[data-v-777ec908]{display:flex;flex-wrap:wrap;gap:8px}.vd-upload-item[data-v-777ec908]{display:flex;align-items:center;flex-wrap:wrap;gap:6px;max-width:100%}.vd-upload-item img[data-v-777ec908]{width:72px;height:72px;object-fit:contain}.vd-upload-item a[data-v-777ec908]{overflow-wrap:anywhere}.vd-upload button[data-v-777ec908]{cursor:pointer;border:1px solid #dcdfe6;border-radius:4px;background:#fff;padding:5px 10px;color:#409eff}.vd-cover-placeholder[data-v-52989853]{display:inline-flex;align-items:center;justify-content:center;min-height:32px;color:#909399;font-size:12px}.vd-login[data-v-a32e9ae3]{max-width:400px;margin:80px auto;padding:24px}h1[data-v-a32e9ae3]{font-size:24px}.vd-home[data-v-e81cec03]{padding:var(--vd-home-padding, 24px)}h1[data-v-e81cec03]{margin:0 0 12px;font-size:24px;font-weight:500}p[data-v-e81cec03]{margin:0;color:var(--el-text-color-secondary, #909399)}.vd-batch-actions[data-v-bf15a101]{display:flex;align-items:center;gap:6px;margin:4px 0}.vd-model-search[data-v-d6cb8ac9]{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px;margin-bottom:4px}.vd-search-field[data-v-d6cb8ac9]{flex:0 1 var(--search-field-width, 8rem);width:var(--search-field-width, 8rem);min-width:0;max-width:100%}.vd-search-field[data-v-d6cb8ac9] .el-input,.vd-search-field[data-v-d6cb8ac9] .el-select,.vd-search-field[data-v-d6cb8ac9] .el-input-number,.vd-search-field[data-v-d6cb8ac9] .el-date-editor{width:100%;min-width:0}.vd-search-range[data-v-d6cb8ac9]{display:flex;align-items:center;gap:4px}.foreignkey-link[data-v-72c5023a]{color:var(--el-color-primary, #409eff);text-decoration:none}.foreignkey-link[data-v-72c5023a]:hover{text-decoration:underline}.foreignkey-link[data-v-75351597]{color:var(--el-color-primary, #409eff);text-decoration:none}.foreignkey-link[data-v-75351597]:hover{text-decoration:underline}.vd-table-text[data-v-286ae7d1]{white-space:pre-wrap;overflow-wrap:anywhere}.vd-table-boolean.is-true[data-v-286ae7d1]{color:var(--el-color-success, green);font-weight:700}.vd-table-images[data-v-286ae7d1]{display:flex;flex-wrap:wrap;gap:4px}.vd-table-video[data-v-286ae7d1]{width:180px;max-width:100%}.vd-table-json[data-v-286ae7d1]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;margin:0}.vd-table-json dd[data-v-286ae7d1]{margin:0}@media(hover:hover)and (pointer:fine){[data-v-1a20346e] .vd-row-actions.hover-show{visibility:hidden}[data-v-1a20346e] .el-table__row:hover .vd-row-actions.hover-show{visibility:visible;cursor:pointer}}.vd-table-actions[data-v-1a20346e]{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-bottom:8px}.vd-table-pagination[data-v-aa3ecf07]{padding:10px 0 6px}.vd-table-pagination[data-v-aa3ecf07] .el-pagination{display:flex;flex-wrap:wrap;justify-content:flex-start;row-gap:8px}.vd-table-pagination.is-mobile[data-v-aa3ecf07]{padding:12px 0}@media(max-width:600px){.vd-table-pagination[data-v-aa3ecf07] .el-pagination__jump{margin-left:0}}