mirror of
https://github.com/fastapi-admin/fastapi-admin.git
synced 2026-03-13 10:32:25 +08:00
json view
add example update pypi.yml
This commit is contained in:
13
.github/workflows/pypi.yml
vendored
13
.github/workflows/pypi.yml
vendored
@@ -21,4 +21,15 @@ jobs:
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_password }}
|
||||
password: ${{ secrets.pypi_password }}
|
||||
- name: Deploy
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
script: |
|
||||
cd /root/fastapi-admin/
|
||||
git pull
|
||||
supervisorctl restart fastapi-admin
|
||||
@@ -13,3 +13,15 @@ class ProductType(EnumMixin, IntEnum):
|
||||
cls.article: 'Article',
|
||||
cls.page: 'Page'
|
||||
}
|
||||
|
||||
|
||||
class Status(EnumMixin, IntEnum):
|
||||
on = 1
|
||||
off = 0
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
return {
|
||||
cls.on: 'On',
|
||||
cls.off: 'Off'
|
||||
}
|
||||
|
||||
@@ -307,3 +307,32 @@ VALUES (2, 'admin', '$2b$12$mrRdNt8n5V8Lsmdh8OGCEOh3.xkUzJRbTo0Ew8IcdyNHjRTfJ0pt
|
||||
COMMIT;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `config`;
|
||||
CREATE TABLE `config`
|
||||
(
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`label` varchar(20) NOT NULL,
|
||||
`key` varchar(50) NOT NULL,
|
||||
`value` longtext NOT NULL,
|
||||
`status` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `key` (`key`)
|
||||
) ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 8
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of config
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `config`
|
||||
VALUES (1, 'test', 'test',
|
||||
'{"status":200,"error":"","data":[{"news_id":51184,"title":"iPhone X Review: Innovative future with real black technology","source":"Netease phone"},{"news_id":51183,"title":"Traffic paradise: How to design streets for people and unmanned vehicles in the future?","source":"Netease smart"},{"news_id":51182,"title":"Teslamask''s American Business Relations: The government does not pay billions to build factories","source":"AI Finance","members":["Daniel","Mike","John"]}]}',
|
||||
1);
|
||||
COMMIT;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -100,6 +100,11 @@ def create_app():
|
||||
url='/rest/Category',
|
||||
icon='icon-list'
|
||||
),
|
||||
Menu(
|
||||
name='Config',
|
||||
url='/rest/Config',
|
||||
icon='fa fa-pencil'
|
||||
),
|
||||
Menu(
|
||||
name='External',
|
||||
title=True
|
||||
|
||||
@@ -3,7 +3,7 @@ import datetime
|
||||
from tortoise import fields, Model
|
||||
|
||||
from fastapi_admin.models import User as AdminUser, Permission, Role
|
||||
from .enums import ProductType
|
||||
from .enums import ProductType, Status
|
||||
|
||||
|
||||
class User(AdminUser):
|
||||
@@ -40,3 +40,13 @@ class Product(Model):
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.name}'
|
||||
|
||||
|
||||
class Config(Model):
|
||||
label = fields.CharField(max_length=200)
|
||||
key = fields.CharField(max_length=20)
|
||||
value = fields.JSONField()
|
||||
status: Status = fields.IntEnumField(Status, default=Status.on)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.label}'
|
||||
|
||||
@@ -178,7 +178,8 @@ class AdminApp(FastAPI):
|
||||
)
|
||||
return pk, fields, search_fields_ret
|
||||
|
||||
async def get_resource(self, resource: str, exclude_pk=False, exclude_m2m_field=True, exclude_actions=False):
|
||||
async def get_resource(self, resource: str, exclude_pk=False, exclude_m2m_field=True,
|
||||
exclude_actions=False) -> Resource:
|
||||
assert self._inited, 'must call init() first!'
|
||||
model = getattr(self.models, resource) # type:Type[Model]
|
||||
model_describe = model.describe(serializable=False)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"vue-element-loading": "^1.1.5",
|
||||
"vue-html5-editor": "^1.1.1",
|
||||
"vue-i18n": "^8.17.3",
|
||||
"vue-json-pretty": "^1.6.3",
|
||||
"vue-router": "^3.1.3",
|
||||
"vue-select": "^3.2.0",
|
||||
"vue-snotify": "^3.2.1",
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
</template>
|
||||
<b-img class="type-image" v-else :src="preview(value)" v-bind="field" fluid @click.stop="previewInModal(value)"/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="['json'].includes(field.type)">
|
||||
<b-json-pretty
|
||||
:value=value
|
||||
>
|
||||
</b-json-pretty>
|
||||
</template>
|
||||
<template v-else-if="['audio', 'video'].includes(field.type)">
|
||||
<component :is="field.type" :src="value" controls/>
|
||||
</template>
|
||||
@@ -87,8 +92,10 @@
|
||||
|
||||
<script>
|
||||
import _ from "lodash";
|
||||
import BJsonPretty from "./JsonPretty";
|
||||
|
||||
export default {
|
||||
components: {BJsonPretty},
|
||||
data() {
|
||||
return {
|
||||
previewValue: null,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
v-if="inline"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<input type="hidden" name="token" :value="auth.token" />
|
||||
<input type="hidden" name="token" :value="auth.token"/>
|
||||
|
||||
<template v-for="(field, name) in fields">
|
||||
<label
|
||||
@@ -40,7 +40,8 @@
|
||||
variant="secondary"
|
||||
@click="$router.go(-1)"
|
||||
v-if="backText"
|
||||
>{{backText}}</b-button>
|
||||
>{{backText}}
|
||||
</b-button>
|
||||
<slot name="extra-buttons"></slot>
|
||||
</slot>
|
||||
</component>
|
||||
@@ -55,7 +56,7 @@
|
||||
enctype="multipart/form-data"
|
||||
v-else
|
||||
>
|
||||
<input type="hidden" name="token" :value="auth.token" />
|
||||
<input type="hidden" name="token" :value="auth.token"/>
|
||||
<b-tabs
|
||||
class="my-3"
|
||||
v-if="groupBy"
|
||||
@@ -140,233 +141,236 @@
|
||||
variant="secondary"
|
||||
@click="$router.go(-1)"
|
||||
v-if="backText"
|
||||
>{{backText}}</b-button>
|
||||
>{{backText}}
|
||||
</b-button>
|
||||
</slot>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from "lodash";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
name: "b-form-builder",
|
||||
components: {},
|
||||
props: {
|
||||
parent: {},
|
||||
subForm: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default() {
|
||||
return "form_" + parseInt(Math.random() * 9999);
|
||||
}
|
||||
},
|
||||
auth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
col: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
languages: {},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
useFormData: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
submitRawForm: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
groupBy: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
required: true,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
required: false,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
onSubmit: {
|
||||
type: Function,
|
||||
required: false
|
||||
},
|
||||
beforeSubmit: {
|
||||
type: Function,
|
||||
required: false
|
||||
},
|
||||
action: {},
|
||||
method: {
|
||||
default: "post"
|
||||
},
|
||||
submitText: {
|
||||
default() {
|
||||
return this.$t ? this.$t("actions.save") : "Submit";
|
||||
}
|
||||
},
|
||||
backText: {
|
||||
default() {
|
||||
return this.$t ? this.$t("actions.back") : "Back";
|
||||
}
|
||||
},
|
||||
|
||||
successMessage: {
|
||||
default() {
|
||||
return this.$t ? this.$t("messages.succeed") : "Succeed";
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
model: null,
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {
|
||||
tag() {
|
||||
return this.subForm ? "div" : "form";
|
||||
},
|
||||
actionUrl() {
|
||||
return global.API_URI + this.action;
|
||||
},
|
||||
groupedFields() {
|
||||
const ret = {};
|
||||
_.keys(_.groupBy(this.fields, this.groupBy)).map(v => {
|
||||
let tabName = v;
|
||||
if (v === "undefined") {
|
||||
v = null;
|
||||
tabName = this.$t("messages.default");
|
||||
}
|
||||
ret[tabName] = _.pickBy(this.fields, field => field.group == v);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFieldId(name) {
|
||||
if (this.subForm) {
|
||||
return `input_${this.subForm}_${name}`;
|
||||
}
|
||||
return `input_${name}`;
|
||||
},
|
||||
getFieldName(name) {
|
||||
if (this.subForm) {
|
||||
return `${this.subForm}[${name}]`;
|
||||
}
|
||||
return name;
|
||||
},
|
||||
setValue(name, value, lang) {
|
||||
const isIntl = this.fields[name].multilingual || this.fields[name].intl;
|
||||
if (!isIntl) {
|
||||
this.$set(this.model, name, value);
|
||||
// _.set(this.model, name, value);
|
||||
} else if (lang && !_.isObject(this.model[name])) {
|
||||
this.$set(this.model, name, {});
|
||||
} else {
|
||||
this.$set(this.model[name], lang, value);
|
||||
}
|
||||
return this.$emit("input", this.model);
|
||||
},
|
||||
titlize() {},
|
||||
isShowField(field) {
|
||||
return (
|
||||
!field.showWhen || this.model[field.showWhen] || eval(field.showWhen)
|
||||
);
|
||||
},
|
||||
getInputClass() {
|
||||
return [];
|
||||
// const classNames = [];
|
||||
// classNames.push(`col-lg-${field.input_cols ? field.input_cols : "12"}`);
|
||||
// return classNames;
|
||||
},
|
||||
getClass(field) {
|
||||
const cols = field.cols ? field.cols : 12;
|
||||
const classNames = ["col-lg-" + cols, "col-" + Math.min(12, cols * 2)];
|
||||
return classNames;
|
||||
},
|
||||
hasError(name) {
|
||||
return _.find(this.errors, v => v.field == name);
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.submitButton.click();
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.beforeSubmit) {
|
||||
const ret = this.beforeSubmit(this.model);
|
||||
if (ret === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.submitRawForm) {
|
||||
this.$refs.form.submit();
|
||||
return true;
|
||||
}
|
||||
if (this.onSubmit) {
|
||||
return this.onSubmit(this.model);
|
||||
}
|
||||
const methodName = String(this.method).toLowerCase();
|
||||
let formData = this.model;
|
||||
if (this.useFormData) {
|
||||
formData = new FormData();
|
||||
_.mapValues(this.model, (v, k) => formData.append(k, v));
|
||||
}
|
||||
this.$http[methodName](this.action, formData)
|
||||
.then(({ data }) => {
|
||||
if (this.successMessage) {
|
||||
this.$snotify.success(this.successMessage);
|
||||
}
|
||||
this.errors = [];
|
||||
this.$emit("success", data);
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
if (status == 422) {
|
||||
this.errors = data.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.model = Object.assign({}, this.value);
|
||||
|
||||
for (let [k, v] of Object.entries(this.fields)) {
|
||||
if (v.type === "object" && !this.model[k]) {
|
||||
this.$set(this.model, k, {});
|
||||
}
|
||||
}
|
||||
|
||||
this.$watch(
|
||||
"value",
|
||||
val => {
|
||||
this.model = Object.assign({}, val);
|
||||
export default {
|
||||
name: "b-form-builder",
|
||||
components: {},
|
||||
props: {
|
||||
parent: {},
|
||||
subForm: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
// global.console.log(this.fields, this.model)
|
||||
},
|
||||
created() {}
|
||||
};
|
||||
id: {
|
||||
type: String,
|
||||
default() {
|
||||
return "form_" + parseInt(Math.random() * 9999);
|
||||
}
|
||||
},
|
||||
auth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
col: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
languages: {},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
useFormData: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
submitRawForm: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
groupBy: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
required: true,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
required: false,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
onSubmit: {
|
||||
type: Function,
|
||||
required: false
|
||||
},
|
||||
beforeSubmit: {
|
||||
type: Function,
|
||||
required: false
|
||||
},
|
||||
action: {},
|
||||
method: {
|
||||
default: "post"
|
||||
},
|
||||
submitText: {
|
||||
default() {
|
||||
return this.$t ? this.$t("actions.save") : "Submit";
|
||||
}
|
||||
},
|
||||
backText: {
|
||||
default() {
|
||||
return this.$t ? this.$t("actions.back") : "Back";
|
||||
}
|
||||
},
|
||||
|
||||
successMessage: {
|
||||
default() {
|
||||
return this.$t ? this.$t("messages.succeed") : "Succeed";
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
model: null,
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {
|
||||
tag() {
|
||||
return this.subForm ? "div" : "form";
|
||||
},
|
||||
actionUrl() {
|
||||
return global.API_URI + this.action;
|
||||
},
|
||||
groupedFields() {
|
||||
const ret = {};
|
||||
_.keys(_.groupBy(this.fields, this.groupBy)).map(v => {
|
||||
let tabName = v;
|
||||
if (v === "undefined") {
|
||||
v = null;
|
||||
tabName = this.$t("messages.default");
|
||||
}
|
||||
ret[tabName] = _.pickBy(this.fields, field => field.group == v);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFieldId(name) {
|
||||
if (this.subForm) {
|
||||
return `input_${this.subForm}_${name}`;
|
||||
}
|
||||
return `input_${name}`;
|
||||
},
|
||||
getFieldName(name) {
|
||||
if (this.subForm) {
|
||||
return `${this.subForm}[${name}]`;
|
||||
}
|
||||
return name;
|
||||
},
|
||||
setValue(name, value, lang) {
|
||||
const isIntl = this.fields[name].multilingual || this.fields[name].intl;
|
||||
if (!isIntl) {
|
||||
this.$set(this.model, name, value);
|
||||
// _.set(this.model, name, value);
|
||||
} else if (lang && !_.isObject(this.model[name])) {
|
||||
this.$set(this.model, name, {});
|
||||
} else {
|
||||
this.$set(this.model[name], lang, value);
|
||||
}
|
||||
return this.$emit("input", this.model);
|
||||
},
|
||||
titlize() {
|
||||
},
|
||||
isShowField(field) {
|
||||
return (
|
||||
!field.showWhen || this.model[field.showWhen] || eval(field.showWhen)
|
||||
);
|
||||
},
|
||||
getInputClass() {
|
||||
return [];
|
||||
// const classNames = [];
|
||||
// classNames.push(`col-lg-${field.input_cols ? field.input_cols : "12"}`);
|
||||
// return classNames;
|
||||
},
|
||||
getClass(field) {
|
||||
const cols = field.cols ? field.cols : 12;
|
||||
const classNames = ["col-lg-" + cols, "col-" + Math.min(12, cols * 2)];
|
||||
return classNames;
|
||||
},
|
||||
hasError(name) {
|
||||
return _.find(this.errors, v => v.field == name);
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.submitButton.click();
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.beforeSubmit) {
|
||||
const ret = this.beforeSubmit(this.model);
|
||||
if (ret === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.submitRawForm) {
|
||||
this.$refs.form.submit();
|
||||
return true;
|
||||
}
|
||||
if (this.onSubmit) {
|
||||
return this.onSubmit(this.model);
|
||||
}
|
||||
const methodName = String(this.method).toLowerCase();
|
||||
let formData = this.model;
|
||||
if (this.useFormData) {
|
||||
formData = new FormData();
|
||||
_.mapValues(this.model, (v, k) => formData.append(k, v));
|
||||
}
|
||||
this.$http[methodName](this.action, formData)
|
||||
.then(({data}) => {
|
||||
if (this.successMessage) {
|
||||
this.$snotify.success(this.successMessage);
|
||||
}
|
||||
this.errors = [];
|
||||
this.$emit("success", data);
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
if (status == 422) {
|
||||
this.errors = data.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.model = Object.assign({}, this.value);
|
||||
|
||||
for (let [k, v] of Object.entries(this.fields)) {
|
||||
if (v.type === "object" && !this.model[k]) {
|
||||
this.$set(this.model, k, {});
|
||||
}
|
||||
}
|
||||
|
||||
this.$watch(
|
||||
"value",
|
||||
val => {
|
||||
this.model = Object.assign({}, val);
|
||||
},
|
||||
{deep: true}
|
||||
);
|
||||
// global.console.log(this.fields, this.model)
|
||||
},
|
||||
created() {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
24
front/src/components/JsonPretty.vue
Normal file
24
front/src/components/JsonPretty.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<vue-json-pretty
|
||||
:data=value
|
||||
:highlightMouseoverNode="true"
|
||||
>
|
||||
</vue-json-pretty>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import VueJsonPretty from 'vue-json-pretty'
|
||||
|
||||
export default {
|
||||
name: "b-json-pretty",
|
||||
components: {
|
||||
VueJsonPretty
|
||||
},
|
||||
props: {
|
||||
value: null,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ import _ from 'lodash'
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import router from './router'
|
||||
import store, { types } from './store'
|
||||
import store, {types} from './store'
|
||||
import './http'
|
||||
import i18n from './i18n'
|
||||
import inflection from 'inflection'
|
||||
|
||||
Vue.prototype.$inflection = inflection
|
||||
|
||||
import { sync } from 'vuex-router-sync'
|
||||
import {sync} from 'vuex-router-sync'
|
||||
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
import Snotify from 'vue-snotify'
|
||||
@@ -22,6 +23,7 @@ import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import './scss/style.scss'
|
||||
|
||||
import "vue-snotify/styles/material.css"
|
||||
|
||||
Vue.use(Snotify)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
@@ -30,9 +32,11 @@ Vue.use(BootstrapVue)
|
||||
import './form'
|
||||
|
||||
import storage from './storage'
|
||||
|
||||
Vue.prototype.$storage = storage
|
||||
|
||||
import VueElementLoading from 'vue-element-loading'
|
||||
|
||||
Vue.component('BLoading', VueElementLoading)
|
||||
|
||||
Vue.prototype._ = _
|
||||
@@ -47,12 +51,8 @@ new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
watch: {},
|
||||
methods: {},
|
||||
render: h => h(App),
|
||||
|
||||
created() {
|
||||
|
||||
@@ -8766,6 +8766,11 @@ vue-i18n@^8.17.0, vue-i18n@^8.17.3:
|
||||
resolved "https://registry.npm.taobao.org/vue-i18n/download/vue-i18n-8.17.3.tgz?cache=0&sync_timestamp=1587630870936&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-i18n%2Fdownload%2Fvue-i18n-8.17.3.tgz#f366082d5784c3c35e8ffda733cb3f3990a3900d"
|
||||
integrity sha1-82YILVeEw8Nej/2nM8s/OZCjkA0=
|
||||
|
||||
vue-json-pretty@^1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.npm.taobao.org/vue-json-pretty/download/vue-json-pretty-1.6.3.tgz#c7f378f3c9f68977047de28197735bc2cf81b15b"
|
||||
integrity sha1-x/N488n2iXcEfeKBl3Nbws+BsVs=
|
||||
|
||||
vue-loader@^15.9.1:
|
||||
version "15.9.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.1.tgz#bd2ab8f3d281e51d7b81d15390a58424d142243e"
|
||||
|
||||
Reference in New Issue
Block a user